Compare commits
1 Commits
main
...
takt/475/i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a38879dfec |
@ -40,6 +40,7 @@ TAKT (TAKT Agent Koordination Topology) is a multi-agent orchestration system fo
|
||||
| `takt prompt [piece]` | Preview assembled prompts for each movement and phase |
|
||||
| `takt catalog [type]` | List available facets (personas, policies, knowledge, etc.) |
|
||||
| `takt export-cc` | Export takt pieces/agents as Claude Code Skill (~/.claude/) |
|
||||
| `takt export-codex` | Export takt skill files to Codex Skill (~/.agents/skills/takt/) |
|
||||
| `takt reset config` | Reset global config to builtin template |
|
||||
| `takt reset categories` | Reset piece categories to builtin defaults |
|
||||
| `takt metrics review` | Show review quality metrics |
|
||||
@ -270,6 +271,7 @@ builtins/ # Bundled defaults (builtin, read from dist/ at runtim
|
||||
ja/ # Japanese (same structure)
|
||||
project/ # Project-level template files
|
||||
skill/ # Claude Code skill files
|
||||
skill-codex/ # Codex skill files
|
||||
```
|
||||
|
||||
Builtin resources are embedded in the npm package (`builtins/`). Project files in `.takt/` take highest priority, then user files in `~/.takt/`, then builtins. Use `takt eject` to copy builtins for customization.
|
||||
|
||||
203
builtins/skill-codex/SKILL.md
Normal file
203
builtins/skill-codex/SKILL.md
Normal file
@ -0,0 +1,203 @@
|
||||
---
|
||||
name: takt
|
||||
description: >
|
||||
TAKT ピースエンジン。codex exec でサブエージェントを起動し、ピース YAML ワークフローに従って
|
||||
マルチエージェントオーケストレーションを実行する。
|
||||
---
|
||||
|
||||
# TAKT Piece Engine
|
||||
|
||||
## 引数の解析
|
||||
|
||||
$ARGUMENTS を以下のように解析する:
|
||||
|
||||
```
|
||||
$takt {piece} [permission] {task...}
|
||||
```
|
||||
|
||||
- **第1トークン**: ピース名またはYAMLファイルパス(必須)
|
||||
- **第2トークン**: 権限モード(任意)。以下のキーワードの場合は権限モードとして解釈する:
|
||||
- `--permit-full` - `codex exec --sandbox danger-full-access`
|
||||
- `--permit-edit` - `codex exec --full-auto`
|
||||
- 上記以外 → タスク内容の一部として扱う
|
||||
- **残りのトークン**: タスク内容(省略時は AskUserQuestion でユーザーに入力を求める)
|
||||
- **権限モード省略時のデフォルト**: `default`(`codex exec (オプションなし)`)
|
||||
|
||||
例:
|
||||
- `$takt coding FizzBuzzを作って` → coding ピース、default 権限
|
||||
- `$takt coding --permit-full FizzBuzzを作って` → coding ピース、danger-full-access
|
||||
- `$takt /path/to/custom.yaml 実装して` → カスタムYAML、default 権限
|
||||
|
||||
## 事前準備: リファレンスの読み込み
|
||||
|
||||
手順を開始する前に、以下の2ファイルを **Read tool で読み込む**:
|
||||
|
||||
1. `~/.agents/skills/takt/references/engine.md` - プロンプト構築、レポート管理、ループ検出の詳細
|
||||
2. `~/.agents/skills/takt/references/yaml-schema.md` - ピースYAMLの構造定義
|
||||
|
||||
## あなたの役割: Team Lead
|
||||
|
||||
あなたは **Team Lead(オーケストレーター)** である。
|
||||
ピースYAMLに定義されたワークフロー(状態遷移マシン)に従って movement を実行する。
|
||||
|
||||
### 禁止事項
|
||||
|
||||
- **自分で作業するな** - コーディング、レビュー、設計、テスト等は全てサブエージェントに委任する
|
||||
- **タスクを自分で分析して1つの実装にまとめるな** - movement を1つずつ順番に実行せよ
|
||||
- **movement をスキップするな** - 必ず initial_movement から開始し、Rule 評価で決まった次の movement に進む
|
||||
- **"yolo" をピース名と誤解するな** - "yolo" は YOLO(You Only Live Once)の俗語で「無謀・適当・いい加減」という意味。「yolo ではレビューして」= 「適当にやらずにちゃんとレビューして」という意味であり、ピース作成の指示ではない
|
||||
|
||||
### あなたの仕事は4つだけ
|
||||
|
||||
1. ピースYAML を読んでワークフローを理解する
|
||||
2. 各 movement のプロンプトを構築する(references/engine.md 参照)
|
||||
3. **Write tool + Bash tool (`codex exec`)** でサブエージェントを起動して作業を委任する
|
||||
4. サブエージェントの出力から Rule 評価を行い、次の movement を決定する
|
||||
|
||||
**重要**: ユーザーが明示的に指示するまで git commit を実行してはならない。実装完了 ≠ コミット許可。
|
||||
|
||||
### ツールの使い分け(重要)
|
||||
|
||||
| やること | 使うツール | 説明 |
|
||||
|---------|-----------|------|
|
||||
| プロンプト一時保存 | **Write** tool | movement名を含めない安全なランダム名(例: `/tmp/takt-prompt-{timestamp}-{uuid}.md`)で書き出す |
|
||||
| サブエージェント起動 | **Bash** tool | `codex exec {権限オプション} - < /tmp/...` を実行 |
|
||||
|
||||
## 手順(この順序で厳密に実行せよ)
|
||||
|
||||
### 手順 1: ピース解決と読み込み
|
||||
|
||||
引数の第1トークンからピースYAMLファイルを特定して Read で読む。
|
||||
|
||||
**第1トークンがない場合(ピース名未指定):**
|
||||
→ ユーザーに「ピース名を指定してください。例: `$takt coding タスク内容`」と伝えて終了する。
|
||||
|
||||
**ピースYAMLの検索順序:**
|
||||
1. `.yaml` / `.yml` で終わる、または `/` を含む → ファイルパスとして直接 Read
|
||||
2. ピース名として検索:
|
||||
- `~/.takt/pieces/{name}.yaml` (ユーザーカスタム、優先)
|
||||
- `~/.agents/skills/takt/pieces/{name}.yaml` (Skill同梱ビルトイン)
|
||||
3. 見つからない場合: 上記2ディレクトリを Glob で列挙し、AskUserQuestion で選択させる
|
||||
|
||||
YAMLから以下を抽出する(→ references/yaml-schema.md 参照):
|
||||
- `name`, `max_movements`, `initial_movement`, `movements` 配列
|
||||
- セクションマップ: `personas`, `policies`, `instructions`, `output_contracts`, `knowledge`
|
||||
|
||||
### 手順 2: セクションリソースの事前読み込み
|
||||
|
||||
ピースYAMLのセクションマップ(`personas:`, `policies:`, `instructions:`, `output_contracts:`, `knowledge:`)から全ファイルパスを収集する。
|
||||
パスは **ピースYAMLファイルのディレクトリからの相対パス** で解決する。
|
||||
|
||||
例: ピースが `~/.agents/skills/takt/pieces/default.yaml` にあり、`personas:` に `coder: ../facets/personas/coder.md` がある場合
|
||||
→ 絶対パスは `~/.agents/skills/takt/facets/personas/coder.md`
|
||||
|
||||
重複を除いて Read で全て読み込む。読み込んだ内容はサブエージェントへのプロンプト構築に使う。
|
||||
|
||||
### 手順 3: 初期化
|
||||
|
||||
`initial_movement` の名前を確認し、`movements` 配列から該当する movement を取得する。
|
||||
**以下の変数を初期化する:**
|
||||
- `iteration = 1`
|
||||
- `current_movement = initial_movement の movement 定義`
|
||||
- `previous_response = ""`
|
||||
- `permission_mode = コマンドで解析された権限モード("danger-full-access" / "full-auto" / "default")`
|
||||
- `movement_history = []`(遷移履歴。Loop Monitor 用)
|
||||
|
||||
**実行ディレクトリ**: いずれかの movement に `report` フィールドがある場合、`.takt/runs/{YYYYMMDD-HHmmss}-{slug}/` を作成し、以下を配置する。
|
||||
- `reports/`(レポート出力)
|
||||
- `context/knowledge/`(Knowledge スナップショット)
|
||||
- `context/policy/`(Policy スナップショット)
|
||||
- `context/previous_responses/`(Previous Response 履歴 + `latest.md`)
|
||||
- `logs/`(実行ログ)
|
||||
- `meta.json`(run メタデータ)
|
||||
|
||||
レポート出力先パスを `report_dir` 変数(`.takt/runs/{slug}/reports`)として保持する。
|
||||
|
||||
次に **手順 4** に進む。
|
||||
|
||||
### 手順 4: サブエージェント起動
|
||||
|
||||
**iteration が max_movements を超えていたら → 手順 7(ABORT: イテレーション上限)に進む。**
|
||||
|
||||
current_movement のプロンプトを構築する(→ references/engine.md のプロンプト構築を参照)。
|
||||
|
||||
プロンプト構築の要素:
|
||||
1. **ペルソナ**: `persona:` キー → `personas:` セクション → .md ファイル内容
|
||||
2. **ポリシー**: `policy:` キー → `policies:` セクション → .md ファイル内容(複数可、末尾にリマインダー再掲)
|
||||
3. **実行コンテキスト**: cwd, ピース名, movement名, イテレーション情報
|
||||
4. **ナレッジ**: `knowledge:` キー → `knowledge:` セクション → .md ファイル内容
|
||||
5. **インストラクション**: `instruction:` キー → `instructions:` セクション → .md ファイル内容(テンプレート変数展開済み)
|
||||
6. **タスク/前回出力/レポート指示/タグ指示**: 自動注入
|
||||
|
||||
**通常 movement の場合(parallel フィールドなし):**
|
||||
|
||||
1. Write tool でプロンプトを一時ファイルに保存する。
|
||||
- movement名やsubstep名をファイル名に含めず、`/tmp/takt-prompt-{timestamp}-{uuid}.md` のような安全なランダム名を使う
|
||||
2. Bash tool で `codex exec` を実行する。
|
||||
- `--permit-full` の場合: `codex exec --sandbox danger-full-access - < "$tmp_prompt_file"`
|
||||
- `--permit-edit` の場合: `codex exec --full-auto - < "$tmp_prompt_file"`
|
||||
- デフォルト: `codex exec - < "$tmp_prompt_file"`
|
||||
3. `stdout` をサブエージェント出力として扱う。
|
||||
4. **手順 5** に進む。
|
||||
|
||||
**parallel movement の場合:**
|
||||
|
||||
1. parallel 配列の各サブステップ用プロンプトをそれぞれ安全なランダム名(例: `/tmp/takt-parallel-{timestamp}-{uuid}.md`)で保存する。
|
||||
2. **1つのメッセージで**、サブステップ数分の Bash tool (`codex exec`) を並列実行する。
|
||||
3. 全 `stdout` を収集して **手順 5** に進む。
|
||||
|
||||
```bash
|
||||
# 例: 2サブステップを並列実行
|
||||
codex exec --full-auto - < "$tmp_prompt_file_1"
|
||||
codex exec --full-auto - < "$tmp_prompt_file_2"
|
||||
```
|
||||
|
||||
### 手順 5: レポート抽出と Loop Monitor
|
||||
|
||||
**レポート抽出**(current_movement に `report` フィールドがある場合のみ):
|
||||
サブエージェント出力から ```markdown ブロックを抽出し、Write tool で `{report_dir}/{ファイル名}` に保存する。
|
||||
詳細は references/engine.md の「レポートの抽出と保存」を参照。
|
||||
|
||||
**Loop Monitor チェック**(ピースに `loop_monitors` がある場合のみ):
|
||||
`movement_history` に current_movement の名前を追加する。
|
||||
遷移履歴が loop_monitor の `cycle` パターンに `threshold` 回以上マッチした場合、judge サブエージェントを起動して遷移先をオーバーライドする。
|
||||
詳細は references/engine.md の「Loop Monitors」を参照。
|
||||
|
||||
### 手順 6: Rule 評価
|
||||
|
||||
`codex exec` から返ってきたサブエージェント出力から matched_rule を決定する。
|
||||
|
||||
**通常 movement:**
|
||||
1. 出力に `[STEP:N]` タグがあるか探す(複数ある場合は最後のタグを採用)
|
||||
2. タグがあれば → rules[N] を選択(0始まりインデックス)
|
||||
3. タグがなければ → 出力全体を読み、全 condition と比較して最も近いものを選択
|
||||
|
||||
**parallel movement:**
|
||||
1. 各サブステップの `codex exec` 出力に対して、サブステップの rules で条件マッチを判定
|
||||
2. マッチした condition 文字列を記録
|
||||
3. 親 movement の rules で aggregate 評価:
|
||||
- `all("X")`: 全サブステップが "X" にマッチしたら true
|
||||
- `any("X")`: いずれかのサブステップが "X" にマッチしたら true
|
||||
- `all("X", "Y")`: サブステップ1が "X"、サブステップ2が "Y" にマッチしたら true
|
||||
4. 親 rules を上から順に評価し、最初に true になった rule を選択
|
||||
|
||||
matched_rule が決まったら次に進む。
|
||||
- `next = COMPLETE` → **手順 7(COMPLETE)**
|
||||
- `next = ABORT` → **手順 7(ABORT)**
|
||||
- `next = movement 名` → `previous_response` 更新、`iteration += 1`、次 movement を取得して **手順 4** に戻る
|
||||
|
||||
どの rule にもマッチしなかったら → **手順 7(ABORT: ルール不一致)** に進む。
|
||||
|
||||
### 手順 7: 終了
|
||||
|
||||
ユーザーに結果を報告する:
|
||||
- **COMPLETE**: 最後のサブエージェント出力のサマリーを表示
|
||||
- **ABORT**: 失敗理由を表示
|
||||
- **イテレーション上限**: 強制終了を通知
|
||||
|
||||
## 詳細リファレンス
|
||||
|
||||
| ファイル | 内容 |
|
||||
|---------|------|
|
||||
| `references/engine.md` | プロンプト構築、レポート管理、ループ検出の詳細 |
|
||||
| `references/yaml-schema.md` | ピースYAMLの構造定義とフィールド説明 |
|
||||
6
builtins/skill-codex/agents/openai.yaml
Normal file
6
builtins/skill-codex/agents/openai.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
interface:
|
||||
display_name: "TAKT Piece Engine"
|
||||
short_description: "Multi-agent orchestration via piece YAML workflows"
|
||||
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
228
builtins/skill-codex/references/engine.md
Normal file
228
builtins/skill-codex/references/engine.md
Normal file
@ -0,0 +1,228 @@
|
||||
# TAKT 実行エンジン詳細
|
||||
|
||||
## サブエージェントの起動方法
|
||||
|
||||
全ての movement は `codex exec` でサブエージェントを起動して実行する。
|
||||
**あなた(Team Lead)が直接作業することは禁止。**
|
||||
|
||||
### 実行フロー(Write + Bash)
|
||||
|
||||
1. プロンプト全文を一時ファイルへ保存する
|
||||
2. Bash tool で `codex exec` を実行する
|
||||
3. `stdout` を movement の出力として扱う
|
||||
|
||||
```bash
|
||||
# 例
|
||||
codex exec --full-auto - < "$tmp_prompt_file"
|
||||
```
|
||||
|
||||
### permission_mode のマッピング
|
||||
|
||||
コマンド引数で解析された `permission_mode` を以下にマップして `codex exec` に渡す。
|
||||
|
||||
- `$takt coding --permit-full タスク`
|
||||
- `permission_mode = "danger-full-access"`
|
||||
- 実行: `codex exec --sandbox danger-full-access - < /tmp/...`
|
||||
- `$takt coding --permit-edit タスク`
|
||||
- `permission_mode = "full-auto"`
|
||||
- 実行: `codex exec --full-auto - < /tmp/...`
|
||||
- `$takt coding タスク`
|
||||
- `permission_mode = "default"`
|
||||
- 実行: `codex exec - < /tmp/...`
|
||||
|
||||
## 通常 Movement の実行
|
||||
|
||||
通常の movement(`parallel` フィールドなし)は、`codex exec` を1回実行する。
|
||||
|
||||
1. プロンプトを構築する(後述の「プロンプト構築」参照)
|
||||
2. movement名を含めない安全なランダム名(例: `/tmp/takt-prompt-{timestamp}-{uuid}.md`)で保存する
|
||||
3. `codex exec {権限オプション} - < /tmp/...` を実行する
|
||||
4. `stdout` を受け取り Rule 評価で次 movement を決定する
|
||||
|
||||
## Parallel Movement の実行
|
||||
|
||||
`parallel` フィールドを持つ movement は、複数サブステップを並列実行する。
|
||||
|
||||
### 実行手順
|
||||
|
||||
1. parallel 配列の各サブステップごとにプロンプトを構築する
|
||||
2. 各プロンプトを substep名を含めない安全なランダム名で保存する
|
||||
3. **1つのメッセージで** サブステップ数分の Bash tool(`codex exec`)を並列実行する
|
||||
4. 各 `stdout` を収集する
|
||||
5. 各サブステップの `rules` で条件マッチを判定する
|
||||
6. 親 movement の `rules` で aggregate 評価(`all()` / `any()`)を行う
|
||||
|
||||
### サブステップ条件マッチ判定
|
||||
|
||||
各サブステップ出力に対する判定優先順位:
|
||||
|
||||
1. `[STEP:N]` タグがあればインデックスで照合(最後のタグを採用)
|
||||
2. タグがなければ出力全文と条件文の意味一致で判定
|
||||
|
||||
マッチした condition 文字列を記録し、親 movement の aggregate 評価に使う。
|
||||
|
||||
## セクションマップの解決
|
||||
|
||||
ピースYAMLトップレベルの `personas:`, `policies:`, `instructions:`, `output_contracts:`, `knowledge:` はキーとファイルパスの対応表。movement 内ではキー名で参照する。
|
||||
|
||||
### 解決手順
|
||||
|
||||
1. ピースYAMLを読み込む
|
||||
2. 各セクションマップのパスを、**ピースYAMLファイルのディレクトリ基準**で絶対パスへ変換する
|
||||
3. movement のキー参照(例: `persona: coder`)から実ファイルを Read で取得する
|
||||
|
||||
例: ピースが `~/.agents/skills/takt/pieces/default.yaml` の場合
|
||||
- `personas.coder: ../facets/personas/coder.md` → `~/.agents/skills/takt/facets/personas/coder.md`
|
||||
- `policies.coding: ../facets/policies/coding.md` → `~/.agents/skills/takt/facets/policies/coding.md`
|
||||
- `instructions.plan: ../facets/instructions/plan.md` → `~/.agents/skills/takt/facets/instructions/plan.md`
|
||||
|
||||
## プロンプト構築
|
||||
|
||||
各 movement 実行時、以下を上から順に結合してプロンプトを作る。
|
||||
|
||||
1. ペルソナ(`persona:` 参照先 .md 全文)
|
||||
2. 区切り線 `---`
|
||||
3. ポリシー(`policy:` 参照先 .md。複数可)
|
||||
4. 区切り線 `---`
|
||||
5. 実行コンテキスト(cwd / piece / movement / iteration)
|
||||
6. ナレッジ(`knowledge:` 参照先 .md)
|
||||
7. インストラクション(`instruction:` または `instruction_template:`)
|
||||
8. タスク(`{task}` 未使用時は末尾に自動追加)
|
||||
9. 前回出力(`pass_previous_response: true` のとき)
|
||||
10. レポート出力指示(`report` または `output_contracts.report` があるとき)
|
||||
11. ステータスタグ出力指示(`rules` があるとき)
|
||||
12. ポリシーリマインダー(ポリシーを末尾再掲)
|
||||
|
||||
### テンプレート変数展開
|
||||
|
||||
インストラクション内のプレースホルダーを置換する。
|
||||
|
||||
- `{task}`: ユーザー入力タスク
|
||||
- `{previous_response}`: 前 movement 出力
|
||||
- `{iteration}`: ピース全体イテレーション
|
||||
- `{max_movements}`: 最大イテレーション数
|
||||
- `{movement_iteration}`: 当該 movement 実行回数
|
||||
- `{report_dir}`: `.takt/runs/{slug}/reports`
|
||||
- `{report:ファイル名}`: 指定レポート内容(存在しない場合は `(レポート未作成)`)
|
||||
|
||||
## レポート出力指示と保存
|
||||
|
||||
movement がレポートを要求する場合、プロンプト末尾に必須指示を注入する。
|
||||
|
||||
### 形式1: name + format
|
||||
|
||||
```yaml
|
||||
report:
|
||||
name: 01-plan.md
|
||||
format: plan
|
||||
```
|
||||
|
||||
`output_contracts`(または `report_formats`)のキー参照先内容を読み込み、出力契約として渡す。
|
||||
|
||||
### 形式2: 複数レポート配列
|
||||
|
||||
```yaml
|
||||
report:
|
||||
- Summary: summary.md
|
||||
- Scope: 01-scope.md
|
||||
```
|
||||
|
||||
各レポートを見出し付き ` ```markdown ` ブロックで出力するよう指示する。
|
||||
|
||||
### 抽出と保存(Team Lead が実施)
|
||||
|
||||
`codex exec` 出力から ` ```markdown ` ブロックを抽出し、`{report_dir}/{ファイル名}` に Write で保存する。
|
||||
|
||||
- 実行ディレクトリ: `.takt/runs/{YYYYMMDD-HHmmss}-{slug}/`
|
||||
- 保存先:
|
||||
- `reports/`
|
||||
- `context/knowledge/`
|
||||
- `context/policy/`
|
||||
- `context/previous_responses/`(`latest.md` を含む)
|
||||
- `logs/`
|
||||
- `meta.json`
|
||||
|
||||
## ステータスタグ出力指示
|
||||
|
||||
movement に `rules` がある場合、最後に1つだけタグを出力するよう指示する。
|
||||
|
||||
```text
|
||||
[STEP:0] = {rules[0].condition}
|
||||
[STEP:1] = {rules[1].condition}
|
||||
...
|
||||
```
|
||||
|
||||
- `ai("...")` は括弧を外した条件文を表示する
|
||||
- parallel サブステップでも同様に適用する
|
||||
|
||||
## Rule 評価
|
||||
|
||||
### 通常 Movement
|
||||
|
||||
1. 出力中の `[STEP:N]` を検出(複数なら最後を採用)
|
||||
2. 該当 index の rule を採用
|
||||
3. タグがない場合は全文を読み condition と意味照合して最も近い rule を採用
|
||||
|
||||
### Parallel Movement(Aggregate)
|
||||
|
||||
- `all("X")`: 全サブステップが `X` に一致
|
||||
- `any("X")`: いずれかが `X` に一致
|
||||
- `all("X", "Y")`: サブステップ位置対応で一致
|
||||
|
||||
親 rules を上から順に評価し、最初の一致を採用する。
|
||||
|
||||
### 不一致時
|
||||
|
||||
どの rule にも一致しない場合は ABORT し、不一致理由をユーザーへ報告する。
|
||||
|
||||
## ループ検出
|
||||
|
||||
### 基本
|
||||
|
||||
- 同じ movement が連続3回以上なら警告
|
||||
- `max_movements` 到達で ABORT
|
||||
|
||||
### カウンター
|
||||
|
||||
- `iteration`: 全体実行回数
|
||||
- `movement_iteration[name]`: movement 別実行回数
|
||||
- `consecutive_count[name]`: 連続実行回数
|
||||
|
||||
## Loop Monitors
|
||||
|
||||
`loop_monitors` がある場合、指定サイクルを監視する。
|
||||
|
||||
1. movement 遷移履歴を記録する
|
||||
2. `cycle` が `threshold` 回以上連続出現したら judge を実行する
|
||||
3. judge は `persona` + `instruction_template` + `rules` でプロンプト構築する
|
||||
4. judge も同じく `codex exec` で起動する
|
||||
5. judge の評価結果 `next` で遷移先を上書きする
|
||||
|
||||
## 状態遷移の全体像
|
||||
|
||||
```text
|
||||
[開始]
|
||||
↓
|
||||
ピースYAML読み込み + セクションマップ解決
|
||||
↓
|
||||
実行ディレクトリ作成
|
||||
↓
|
||||
initial_movement 取得
|
||||
↓
|
||||
┌─→ codex exec で movement 実行(通常/parallel)
|
||||
│ ↓
|
||||
│ 出力受信
|
||||
│ ↓
|
||||
│ レポート抽出・保存
|
||||
│ ↓
|
||||
│ Loop Monitor チェック(必要時 judge を codex exec で実行)
|
||||
│ ↓
|
||||
│ Rule 評価(タグ優先、未タグ時は意味照合)
|
||||
│ ↓
|
||||
│ next 決定
|
||||
│ ├── COMPLETE → 終了報告
|
||||
│ ├── ABORT → エラー報告
|
||||
│ └── movement名 → 次 movement
|
||||
│ ↓
|
||||
└──────────────────────┘
|
||||
```
|
||||
224
builtins/skill-codex/references/yaml-schema.md
Normal file
224
builtins/skill-codex/references/yaml-schema.md
Normal file
@ -0,0 +1,224 @@
|
||||
# ピースYAML スキーマリファレンス
|
||||
|
||||
このドキュメントはピースYAMLの構造を定義する。具体的なピース定義は含まない。
|
||||
|
||||
## トップレベルフィールド
|
||||
|
||||
```yaml
|
||||
name: piece-name # ピース名(必須)
|
||||
description: 説明テキスト # ピースの説明(任意)
|
||||
max_movements: 10 # 最大イテレーション数(必須)
|
||||
initial_movement: plan # 最初に実行する movement 名(必須)
|
||||
|
||||
# セクションマップ(キー → ファイルパスの対応表)
|
||||
policies: # ポリシー定義(任意)
|
||||
coding: ../policies/coding.md
|
||||
review: ../policies/review.md
|
||||
personas: # ペルソナ定義(任意)
|
||||
coder: ../personas/coder.md
|
||||
reviewer: ../personas/architecture-reviewer.md
|
||||
instructions: # 指示テンプレート定義(任意)
|
||||
plan: ../instructions/plan.md
|
||||
implement: ../instructions/implement.md
|
||||
report_formats: # レポートフォーマット定義(任意)
|
||||
plan: ../output-contracts/plan.md
|
||||
review: ../output-contracts/architecture-review.md
|
||||
knowledge: # ナレッジ定義(任意)
|
||||
architecture: ../knowledge/architecture.md
|
||||
|
||||
movements: [...] # movement 定義の配列(必須)
|
||||
loop_monitors: [...] # ループ監視設定(任意)
|
||||
```
|
||||
|
||||
### セクションマップの解決
|
||||
|
||||
各セクションマップのパスは **ピースYAMLファイルのディレクトリからの相対パス** で解決する。
|
||||
movement 内では**キー名**で参照する(パスを直接書かない)。
|
||||
|
||||
例: ピースが `~/.claude/skills/takt/pieces/coding.yaml` にあり、`personas:` セクションに `coder: ../personas/coder.md` がある場合
|
||||
→ 絶対パスは `~/.claude/skills/takt/personas/coder.md`
|
||||
→ movement では `persona: coder` で参照
|
||||
|
||||
## Movement 定義
|
||||
|
||||
### 通常 Movement
|
||||
|
||||
```yaml
|
||||
- name: movement-name # movement 名(必須、一意)
|
||||
persona: coder # ペルソナキー(personas マップを参照、任意)
|
||||
policy: coding # ポリシーキー(policies マップを参照、任意)
|
||||
policy: [coding, testing] # 複数指定も可(配列)
|
||||
instruction: implement # 指示テンプレートキー(instructions マップを参照、任意)
|
||||
knowledge: architecture # ナレッジキー(knowledge マップを参照、任意)
|
||||
edit: true # ファイル編集可否(必須)
|
||||
required_permission_mode: edit # 必要最小権限: edit / readonly / full(任意)
|
||||
session: refresh # セッション管理(任意)
|
||||
pass_previous_response: true # 前の出力を渡すか(デフォルト: true)
|
||||
allowed_tools: [...] # 許可ツール一覧(任意、参考情報)
|
||||
instruction_template: | # 指示テンプレート(参照解決またはインライン、任意)
|
||||
指示内容...
|
||||
output_contracts: [...] # 出力契約設定(任意)
|
||||
quality_gates: [...] # 品質ゲート(AIへの指示、任意)
|
||||
rules: [...] # 遷移ルール(必須)
|
||||
```
|
||||
|
||||
**`instruction` vs `instruction_template`**: どちらも同じ参照解決ルート(セクションマップ → パス → 3-layer facet → インライン)を使う。`instruction_template` はインライン文字列もそのまま使える。通常はどちらか一方を使用する。
|
||||
|
||||
### Parallel Movement
|
||||
|
||||
```yaml
|
||||
- name: reviewers # 親 movement 名(必須)
|
||||
parallel: # 並列サブステップ配列(これがあると parallel movement)
|
||||
- name: arch-review
|
||||
persona: architecture-reviewer
|
||||
policy: review
|
||||
knowledge: architecture
|
||||
edit: false
|
||||
instruction: review-arch
|
||||
output_contracts:
|
||||
report:
|
||||
- name: 05-architect-review.md
|
||||
format: architecture-review
|
||||
rules:
|
||||
- condition: "approved"
|
||||
- condition: "needs_fix"
|
||||
|
||||
- name: qa-review
|
||||
persona: qa-reviewer
|
||||
policy: review
|
||||
edit: false
|
||||
instruction: review-qa
|
||||
rules:
|
||||
- condition: "approved"
|
||||
- condition: "needs_fix"
|
||||
|
||||
rules: # 親の rules(aggregate 条件で遷移先を決定)
|
||||
- condition: all("approved")
|
||||
next: supervise
|
||||
- condition: any("needs_fix")
|
||||
next: fix
|
||||
```
|
||||
|
||||
**重要**: サブステップの `rules` は結果分類のための condition 定義のみ。`next` は無視される(親の rules が遷移先を決定)。
|
||||
|
||||
## Rules 定義
|
||||
|
||||
```yaml
|
||||
rules:
|
||||
- condition: 条件テキスト # マッチ条件(必須)
|
||||
next: next-movement # 遷移先 movement 名(必須、サブステップでは任意)
|
||||
requires_user_input: true # ユーザー入力が必要(任意)
|
||||
interactive_only: true # インタラクティブモードのみ(任意)
|
||||
appendix: | # 追加情報(任意)
|
||||
補足テキスト...
|
||||
```
|
||||
|
||||
### Condition 記法
|
||||
|
||||
| 記法 | 説明 | 例 |
|
||||
|-----|------|-----|
|
||||
| 文字列 | AI判定またはタグで照合 | `"タスク完了"` |
|
||||
| `ai("...")` | AI が出力に対して条件を評価 | `ai("コードに問題がある")` |
|
||||
| `all("...")` | 全サブステップがマッチ(parallel 親のみ) | `all("approved")` |
|
||||
| `any("...")` | いずれかがマッチ(parallel 親のみ) | `any("needs_fix")` |
|
||||
| `all("X", "Y")` | 位置対応で全マッチ(parallel 親のみ) | `all("問題なし", "テスト成功")` |
|
||||
|
||||
### 特殊な next 値
|
||||
|
||||
| 値 | 意味 |
|
||||
|---|------|
|
||||
| `COMPLETE` | ピース成功終了 |
|
||||
| `ABORT` | ピース失敗終了 |
|
||||
| movement 名 | 指定された movement に遷移 |
|
||||
|
||||
## Output Contracts 定義
|
||||
|
||||
Movement の出力契約(レポート定義)。`output_contracts.report` 配列形式で指定する。
|
||||
|
||||
### 形式1: name + format(フォーマット参照)
|
||||
|
||||
```yaml
|
||||
output_contracts:
|
||||
report:
|
||||
- name: 01-plan.md
|
||||
format: plan # report_formats マップのキーを参照
|
||||
```
|
||||
|
||||
`format` がキー文字列の場合、トップレベル `report_formats:` セクションから対応する .md ファイルを読み込み、出力契約指示として使用する。
|
||||
|
||||
### 形式1b: name + format(インライン)
|
||||
|
||||
```yaml
|
||||
output_contracts:
|
||||
report:
|
||||
- name: 01-plan.md
|
||||
format: | # インラインでフォーマットを記述
|
||||
# レポートタイトル
|
||||
## セクション
|
||||
{内容}
|
||||
```
|
||||
|
||||
### 形式2: label + path(ラベル付きパス)
|
||||
|
||||
```yaml
|
||||
output_contracts:
|
||||
report:
|
||||
- Summary: summary.md
|
||||
- Scope: 01-scope.md
|
||||
- Decisions: 02-decisions.md
|
||||
```
|
||||
|
||||
各要素のキーがレポート種別名(ラベル)、値がファイル名。
|
||||
|
||||
## Quality Gates 定義
|
||||
|
||||
Movement 完了時の品質要件を AI への指示として定義する。自動検証は行わない。
|
||||
|
||||
```yaml
|
||||
quality_gates:
|
||||
- 全てのテストがパスすること
|
||||
- TypeScript の型エラーがないこと
|
||||
- ESLint 違反がないこと
|
||||
```
|
||||
|
||||
配列で複数の品質基準を指定できる。エージェントはこれらの基準を満たしてから Movement を完了する必要がある。
|
||||
|
||||
## テンプレート変数
|
||||
|
||||
`instruction_template`(またはインストラクションファイル)内で使用可能な変数:
|
||||
|
||||
| 変数 | 説明 |
|
||||
|-----|------|
|
||||
| `{task}` | ユーザーのタスク入力(template に含まれない場合は自動追加) |
|
||||
| `{previous_response}` | 前の movement の出力(pass_previous_response: true 時、自動追加) |
|
||||
| `{iteration}` | ピース全体のイテレーション数 |
|
||||
| `{max_movements}` | 最大イテレーション数 |
|
||||
| `{movement_iteration}` | この movement の実行回数 |
|
||||
| `{report_dir}` | レポートディレクトリ名 |
|
||||
| `{report:ファイル名}` | 指定レポートファイルの内容を展開 |
|
||||
| `{user_inputs}` | 蓄積されたユーザー入力 |
|
||||
| `{cycle_count}` | loop_monitors 内で使用するサイクル回数 |
|
||||
|
||||
## Loop Monitors(任意)
|
||||
|
||||
```yaml
|
||||
loop_monitors:
|
||||
- cycle: [movement_a, movement_b] # 監視対象の movement サイクル
|
||||
threshold: 3 # 発動閾値(サイクル回数)
|
||||
judge:
|
||||
persona: supervisor # ペルソナキー参照
|
||||
instruction_template: | # 判定用指示
|
||||
サイクルが {cycle_count} 回繰り返されました。
|
||||
健全性を判断してください。
|
||||
rules:
|
||||
- condition: 健全(進捗あり)
|
||||
next: movement_a
|
||||
- condition: 非生産的(改善なし)
|
||||
next: alternative_movement
|
||||
```
|
||||
|
||||
特定の movement 間のサイクルが閾値に達した場合、judge が介入して遷移先を判断する。
|
||||
|
||||
## allowed_tools について
|
||||
|
||||
`allowed_tools` は TAKT 本体のエージェントプロバイダーで使用されるフィールド。Claude Code の Skill として実行する場合、Task tool のエージェントが使用可能なツールは Claude Code の設定に従う。このフィールドは参考情報として扱い、`edit` フィールドの方を権限制御に使用する。
|
||||
@ -251,6 +251,15 @@ takt clear
|
||||
takt export-cc
|
||||
```
|
||||
|
||||
### takt export-codex
|
||||
|
||||
TAKT のスキルファイルを Codex Skill(`~/.agents/skills/takt/`)としてデプロイします。
|
||||
このコマンドは `SKILL.md`、`references/`、`agents/`、`pieces/`、`facets/`、`templates/` をデプロイします。
|
||||
|
||||
```bash
|
||||
takt export-codex
|
||||
```
|
||||
|
||||
### takt catalog
|
||||
|
||||
レイヤー間で利用可能なファセットの一覧を表示します。
|
||||
|
||||
@ -251,6 +251,15 @@ Deploy builtin pieces/personas as a Claude Code Skill.
|
||||
takt export-cc
|
||||
```
|
||||
|
||||
### takt export-codex
|
||||
|
||||
Deploy TAKT skill files as a Codex Skill (`~/.agents/skills/takt/`).
|
||||
This command deploys `SKILL.md`, `references/`, `agents/`, `pieces/`, `facets/`, and `templates/`.
|
||||
|
||||
```bash
|
||||
takt export-codex
|
||||
```
|
||||
|
||||
### takt catalog
|
||||
|
||||
List available facets across layers.
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
provider: claude
|
||||
language: en
|
||||
log_level: info
|
||||
logging:
|
||||
level: info
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
|
||||
@ -53,6 +53,7 @@ vi.mock('../app/cli/program.js', () => ({
|
||||
}));
|
||||
|
||||
vi.mock('../infra/config/index.js', () => ({
|
||||
clearPersonaSessions: vi.fn(),
|
||||
resolveConfigValue: vi.fn(),
|
||||
}));
|
||||
|
||||
@ -73,7 +74,6 @@ vi.mock('../features/tasks/index.js', () => ({
|
||||
}));
|
||||
|
||||
vi.mock('../features/config/index.js', () => ({
|
||||
clearPersonaSessions: vi.fn(),
|
||||
ejectBuiltin: vi.fn(),
|
||||
ejectFacet: vi.fn(),
|
||||
parseFacetType: vi.fn(),
|
||||
@ -81,6 +81,7 @@ vi.mock('../features/config/index.js', () => ({
|
||||
resetCategoriesToDefault: vi.fn(),
|
||||
resetConfigToDefault: vi.fn(),
|
||||
deploySkill: vi.fn(),
|
||||
deploySkillCodex: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../features/prompt/index.js', () => ({
|
||||
@ -111,6 +112,7 @@ vi.mock('../commands/repertoire/list.js', () => ({
|
||||
}));
|
||||
|
||||
import '../app/cli/commands.js';
|
||||
const configFeatures = await import('../features/config/index.js');
|
||||
|
||||
describe('CLI add command', () => {
|
||||
beforeEach(() => {
|
||||
@ -151,6 +153,22 @@ describe('CLI add command', () => {
|
||||
expect(calledCommandNames).not.toContain('switch');
|
||||
});
|
||||
|
||||
it('should register export-codex command', () => {
|
||||
const calledCommandNames = rootCommand.command.mock.calls
|
||||
.map((call: unknown[]) => call[0] as string);
|
||||
|
||||
expect(calledCommandNames).toContain('export-codex');
|
||||
});
|
||||
|
||||
it('should invoke deploySkillCodex for export-codex command', async () => {
|
||||
const exportCodexAction = commandActions.get('root.export-codex');
|
||||
expect(exportCodexAction).toBeTypeOf('function');
|
||||
|
||||
await exportCodexAction?.();
|
||||
const deploySkillCodex = (configFeatures as Record<string, unknown>).deploySkillCodex;
|
||||
expect(deploySkillCodex).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should describe prompt piece argument as defaulting to "default"', () => {
|
||||
const promptCommand = commandMocks.get('root.prompt');
|
||||
expect(promptCommand).toBeTruthy();
|
||||
|
||||
205
src/__tests__/deploySkillCodex.test.ts
Normal file
205
src/__tests__/deploySkillCodex.test.ts
Normal file
@ -0,0 +1,205 @@
|
||||
/**
|
||||
* Tests for deploySkillCodex (export-codex) command
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
import { mkdtempSync, mkdirSync, writeFileSync, existsSync, rmSync, readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
|
||||
const testHomeDir = mkdtempSync(join(tmpdir(), 'takt-deploy-codex-test-'));
|
||||
|
||||
vi.mock('node:os', async () => {
|
||||
const actual = await vi.importActual('node:os');
|
||||
return {
|
||||
...actual,
|
||||
homedir: () => testHomeDir,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('../shared/prompt/index.js', () => ({
|
||||
confirm: vi.fn().mockResolvedValue(true),
|
||||
}));
|
||||
|
||||
vi.mock('../shared/ui/index.js', () => ({
|
||||
header: vi.fn(),
|
||||
success: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
blankLine: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../infra/config/index.js', () => ({
|
||||
getLanguage: vi.fn().mockReturnValue('en'),
|
||||
}));
|
||||
|
||||
let fakeResourcesDir: string;
|
||||
|
||||
vi.mock('../infra/resources/index.js', async () => {
|
||||
const actual = await vi.importActual('../infra/resources/index.js');
|
||||
return {
|
||||
...actual,
|
||||
getResourcesDir: () => fakeResourcesDir,
|
||||
getLanguageResourcesDir: (lang: string) => join(fakeResourcesDir, lang),
|
||||
};
|
||||
});
|
||||
|
||||
const configFeatures = await import('../features/config/index.js');
|
||||
const deploySkillCodex = (configFeatures as Record<string, unknown>).deploySkillCodex as () => Promise<void>;
|
||||
const { warn } = await import('../shared/ui/index.js');
|
||||
const { confirm } = await import('../shared/prompt/index.js');
|
||||
|
||||
describe('deploySkillCodex', () => {
|
||||
let skillDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
fakeResourcesDir = mkdtempSync(join(tmpdir(), 'takt-resources-codex-'));
|
||||
|
||||
const skillResourcesDir = join(fakeResourcesDir, 'skill-codex');
|
||||
mkdirSync(skillResourcesDir, { recursive: true });
|
||||
writeFileSync(join(skillResourcesDir, 'SKILL.md'), '# SKILL Codex');
|
||||
|
||||
const refsDir = join(skillResourcesDir, 'references');
|
||||
mkdirSync(refsDir, { recursive: true });
|
||||
writeFileSync(join(refsDir, 'engine.md'), '# Engine');
|
||||
writeFileSync(join(refsDir, 'yaml-schema.md'), '# Schema');
|
||||
|
||||
const agentsDir = join(skillResourcesDir, 'agents');
|
||||
mkdirSync(agentsDir, { recursive: true });
|
||||
writeFileSync(join(agentsDir, 'openai.yaml'), 'interface:\n display_name: TAKT');
|
||||
|
||||
const langDir = join(fakeResourcesDir, 'en');
|
||||
mkdirSync(join(langDir, 'pieces'), { recursive: true });
|
||||
mkdirSync(join(langDir, 'facets', 'personas'), { recursive: true });
|
||||
mkdirSync(join(langDir, 'facets', 'policies'), { recursive: true });
|
||||
mkdirSync(join(langDir, 'facets', 'instructions'), { recursive: true });
|
||||
mkdirSync(join(langDir, 'facets', 'knowledge'), { recursive: true });
|
||||
mkdirSync(join(langDir, 'facets', 'output-contracts'), { recursive: true });
|
||||
mkdirSync(join(langDir, 'templates'), { recursive: true });
|
||||
|
||||
writeFileSync(join(langDir, 'pieces', 'default.yaml'), 'name: default');
|
||||
writeFileSync(join(langDir, 'facets', 'personas', 'coder.md'), '# Coder');
|
||||
writeFileSync(join(langDir, 'facets', 'policies', 'coding.md'), '# Coding');
|
||||
writeFileSync(join(langDir, 'facets', 'instructions', 'init.md'), '# Init');
|
||||
writeFileSync(join(langDir, 'facets', 'knowledge', 'patterns.md'), '# Patterns');
|
||||
writeFileSync(join(langDir, 'facets', 'output-contracts', 'summary.md'), '# Summary');
|
||||
writeFileSync(join(langDir, 'templates', 'task.md'), '# Task');
|
||||
|
||||
skillDir = join(testHomeDir, '.agents', 'skills', 'takt');
|
||||
mkdirSync(skillDir, { recursive: true });
|
||||
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (existsSync(testHomeDir)) {
|
||||
rmSync(testHomeDir, { recursive: true, force: true });
|
||||
}
|
||||
if (existsSync(fakeResourcesDir)) {
|
||||
rmSync(fakeResourcesDir, { recursive: true, force: true });
|
||||
}
|
||||
mkdirSync(testHomeDir, { recursive: true });
|
||||
});
|
||||
|
||||
describe('when codex skill resources exist', () => {
|
||||
it('should copy SKILL.md to codex skill directory', async () => {
|
||||
await deploySkillCodex();
|
||||
|
||||
expect(existsSync(join(skillDir, 'SKILL.md'))).toBe(true);
|
||||
expect(readFileSync(join(skillDir, 'SKILL.md'), 'utf-8')).toBe('# SKILL Codex');
|
||||
});
|
||||
|
||||
it('should copy references directory', async () => {
|
||||
await deploySkillCodex();
|
||||
|
||||
const refsDir = join(skillDir, 'references');
|
||||
expect(existsSync(refsDir)).toBe(true);
|
||||
expect(existsSync(join(refsDir, 'engine.md'))).toBe(true);
|
||||
expect(existsSync(join(refsDir, 'yaml-schema.md'))).toBe(true);
|
||||
});
|
||||
|
||||
it('should copy agents/openai.yaml', async () => {
|
||||
await deploySkillCodex();
|
||||
|
||||
expect(existsSync(join(skillDir, 'agents', 'openai.yaml'))).toBe(true);
|
||||
});
|
||||
|
||||
it('should copy all language resource directories', async () => {
|
||||
await deploySkillCodex();
|
||||
|
||||
expect(existsSync(join(skillDir, 'pieces', 'default.yaml'))).toBe(true);
|
||||
expect(existsSync(join(skillDir, 'facets', 'personas', 'coder.md'))).toBe(true);
|
||||
expect(existsSync(join(skillDir, 'facets', 'policies', 'coding.md'))).toBe(true);
|
||||
expect(existsSync(join(skillDir, 'facets', 'instructions', 'init.md'))).toBe(true);
|
||||
expect(existsSync(join(skillDir, 'facets', 'knowledge', 'patterns.md'))).toBe(true);
|
||||
expect(existsSync(join(skillDir, 'facets', 'output-contracts', 'summary.md'))).toBe(true);
|
||||
expect(existsSync(join(skillDir, 'templates', 'task.md'))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('cleanDir behavior', () => {
|
||||
it('should remove stale files from previous deployments', async () => {
|
||||
const piecesDir = join(skillDir, 'pieces');
|
||||
mkdirSync(piecesDir, { recursive: true });
|
||||
writeFileSync(join(piecesDir, 'stale.yaml'), 'name: stale');
|
||||
|
||||
await deploySkillCodex();
|
||||
|
||||
expect(existsSync(join(piecesDir, 'stale.yaml'))).toBe(false);
|
||||
expect(existsSync(join(piecesDir, 'default.yaml'))).toBe(true);
|
||||
});
|
||||
|
||||
it('should clean agents directory before copy', async () => {
|
||||
const agentsDir = join(skillDir, 'agents');
|
||||
mkdirSync(agentsDir, { recursive: true });
|
||||
writeFileSync(join(agentsDir, 'legacy.yaml'), 'legacy');
|
||||
|
||||
await deploySkillCodex();
|
||||
|
||||
expect(existsSync(join(agentsDir, 'legacy.yaml'))).toBe(false);
|
||||
expect(existsSync(join(agentsDir, 'openai.yaml'))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when codex skill resources do not exist', () => {
|
||||
it('should warn and return early', async () => {
|
||||
rmSync(join(fakeResourcesDir, 'skill-codex'), { recursive: true });
|
||||
|
||||
await deploySkillCodex();
|
||||
|
||||
expect(warn).toHaveBeenCalledWith('Skill resources not found. Ensure takt is installed correctly.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when skill already exists', () => {
|
||||
it('should ask for confirmation before overwriting', async () => {
|
||||
writeFileSync(join(skillDir, 'SKILL.md'), '# Old Skill');
|
||||
|
||||
await deploySkillCodex();
|
||||
|
||||
expect(confirm).toHaveBeenCalledWith(
|
||||
'既存のスキルファイルをすべて削除し、最新版に置き換えます。続行しますか?',
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it('should cancel when user declines confirmation', async () => {
|
||||
vi.mocked(confirm).mockResolvedValueOnce(false);
|
||||
writeFileSync(join(skillDir, 'SKILL.md'), '# Old Skill');
|
||||
|
||||
await deploySkillCodex();
|
||||
|
||||
expect(readFileSync(join(skillDir, 'SKILL.md'), 'utf-8')).toBe('# Old Skill');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when language resources directory is empty', () => {
|
||||
it('should handle missing resource subdirectories gracefully', async () => {
|
||||
const langDir = join(fakeResourcesDir, 'en');
|
||||
rmSync(langDir, { recursive: true });
|
||||
mkdirSync(langDir, { recursive: true });
|
||||
|
||||
await expect(deploySkillCodex()).resolves.not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
49
src/__tests__/deploySkillWrappers.test.ts
Normal file
49
src/__tests__/deploySkillWrappers.test.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
|
||||
vi.mock('../features/config/deploySkillInternal.js', () => ({
|
||||
deploySkillInternal: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
|
||||
const { deploySkill } = await import('../features/config/deploySkill.js');
|
||||
const { deploySkillCodex } = await import('../features/config/deploySkillCodex.js');
|
||||
const { deploySkillInternal } = await import('../features/config/deploySkillInternal.js');
|
||||
|
||||
describe('deploy skill wrappers', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should delegate export-cc configuration to shared deploy implementation', async () => {
|
||||
await deploySkill();
|
||||
|
||||
expect(deploySkillInternal).toHaveBeenCalledTimes(1);
|
||||
expect(deploySkillInternal).toHaveBeenCalledWith({
|
||||
headerTitle: 'takt export-cc — Deploy to Claude Code',
|
||||
skillRootDir: '.claude',
|
||||
skillResourceDirName: 'skill',
|
||||
existingInstallMessage: 'Claude Code Skill が既にインストールされています。',
|
||||
usageCommand: '使い方: /takt <piece-name> <task>',
|
||||
usageExample: '例: /takt passthrough "Hello World テスト"',
|
||||
showReferencesSummary: false,
|
||||
includeAgentsDirectory: false,
|
||||
showAgentsSummary: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should delegate export-codex configuration to shared deploy implementation', async () => {
|
||||
await deploySkillCodex();
|
||||
|
||||
expect(deploySkillInternal).toHaveBeenCalledTimes(1);
|
||||
expect(deploySkillInternal).toHaveBeenCalledWith({
|
||||
headerTitle: 'takt export-codex — Deploy to Codex',
|
||||
skillRootDir: '.agents',
|
||||
skillResourceDirName: 'skill-codex',
|
||||
existingInstallMessage: 'Codex Skill が既にインストールされています。',
|
||||
usageCommand: '使い方: $takt <piece-name> <task>',
|
||||
usageExample: '例: $takt passthrough "Hello World テスト"',
|
||||
showReferencesSummary: true,
|
||||
includeAgentsDirectory: true,
|
||||
showAgentsSummary: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -84,7 +84,7 @@ describe('createIsolatedEnv', () => {
|
||||
const config = parseYaml(configRaw) as Record<string, unknown>;
|
||||
|
||||
expect(config.language).toBe('en');
|
||||
expect(config.log_level).toBe('info');
|
||||
expect(config.logging).toEqual({ level: 'info' });
|
||||
expect(config.notification_sound).toBe(false);
|
||||
expect(config.notification_sound_events).toEqual({
|
||||
iteration_limit: false,
|
||||
@ -175,7 +175,8 @@ describe('createIsolatedEnv', () => {
|
||||
`${isolated.taktDir}/config.yaml`,
|
||||
[
|
||||
'language: en',
|
||||
'log_level: info',
|
||||
'logging:',
|
||||
' level: info',
|
||||
'notification_sound: true',
|
||||
'notification_sound_events: true',
|
||||
].join('\n'),
|
||||
|
||||
58
src/__tests__/skillCodexDocs.test.ts
Normal file
58
src/__tests__/skillCodexDocs.test.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
const skillDoc = readFileSync(join(process.cwd(), 'builtins', 'skill-codex', 'SKILL.md'), 'utf-8');
|
||||
const engineDoc = readFileSync(join(process.cwd(), 'builtins', 'skill-codex', 'references', 'engine.md'), 'utf-8');
|
||||
const schemaDoc = readFileSync(join(process.cwd(), 'builtins', 'skill-codex', 'references', 'yaml-schema.md'), 'utf-8');
|
||||
const sharedSchemaDoc = readFileSync(join(process.cwd(), 'builtins', 'skill', 'references', 'yaml-schema.md'), 'utf-8');
|
||||
|
||||
describe('skill-codex document safety guidance', () => {
|
||||
it('should define required YAML frontmatter for Codex SKILL.md', () => {
|
||||
expect(skillDoc).toMatch(/^---\nname: takt\n/);
|
||||
expect(skillDoc).toContain('description: >');
|
||||
expect(skillDoc).toContain('\n---\n');
|
||||
});
|
||||
|
||||
it('should describe codex exec workflow and permission mapping', () => {
|
||||
expect(skillDoc).toContain('Write tool + Bash tool (`codex exec`)');
|
||||
expect(skillDoc).toContain('codex exec --sandbox danger-full-access');
|
||||
expect(skillDoc).toContain('codex exec --full-auto');
|
||||
expect(skillDoc).toContain('codex exec (オプションなし)');
|
||||
});
|
||||
|
||||
it('should consistently use ~/.agents/skills/takt path base for codex skill resources', () => {
|
||||
expect(skillDoc).toContain('~/.agents/skills/takt/');
|
||||
expect(engineDoc).toContain('~/.agents/skills/takt/');
|
||||
expect(skillDoc).not.toContain('~/.claude/skills/takt/');
|
||||
expect(engineDoc).not.toContain('~/.claude/skills/takt/');
|
||||
});
|
||||
|
||||
it('should remove Task tool instructions from codex-specific engine docs', () => {
|
||||
expect(engineDoc).not.toContain('Task tool');
|
||||
expect(engineDoc).not.toContain('TeamCreate');
|
||||
expect(engineDoc).not.toContain('TeamDelete');
|
||||
expect(engineDoc).toContain('codex exec');
|
||||
});
|
||||
|
||||
it('should require random temp file names instead of movement or substep names', () => {
|
||||
expect(skillDoc).toContain('movement名やsubstep名をファイル名に含めず');
|
||||
expect(engineDoc).toContain('movement名を含めない安全なランダム名');
|
||||
});
|
||||
|
||||
it('should avoid examples that interpolate movement or substep names into shell paths', () => {
|
||||
expect(skillDoc).not.toMatch(/takt-\{movement/i);
|
||||
expect(skillDoc).not.toMatch(/takt-\{substep/i);
|
||||
expect(engineDoc).not.toMatch(/takt-\{movement/i);
|
||||
expect(engineDoc).not.toMatch(/takt-\{substep/i);
|
||||
});
|
||||
|
||||
it('should show quoted temp-file variable usage for codex exec stdin redirection', () => {
|
||||
expect(skillDoc).toContain('< "$tmp_prompt_file"');
|
||||
expect(engineDoc).toContain('< "$tmp_prompt_file"');
|
||||
});
|
||||
|
||||
it('should keep yaml schema identical to shared schema reference', () => {
|
||||
expect(schemaDoc).toBe(sharedSchemaDoc);
|
||||
});
|
||||
});
|
||||
@ -9,7 +9,16 @@ import { clearPersonaSessions, resolveConfigValue } from '../../infra/config/ind
|
||||
import { getGlobalConfigDir } from '../../infra/config/paths.js';
|
||||
import { success, info } from '../../shared/ui/index.js';
|
||||
import { runAllTasks, addTask, watchTasks, listTasks } from '../../features/tasks/index.js';
|
||||
import { ejectBuiltin, ejectFacet, parseFacetType, VALID_FACET_TYPES, resetCategoriesToDefault, resetConfigToDefault, deploySkill } from '../../features/config/index.js';
|
||||
import {
|
||||
ejectBuiltin,
|
||||
ejectFacet,
|
||||
parseFacetType,
|
||||
VALID_FACET_TYPES,
|
||||
resetCategoriesToDefault,
|
||||
resetConfigToDefault,
|
||||
deploySkill,
|
||||
deploySkillCodex,
|
||||
} from '../../features/config/index.js';
|
||||
import { previewPrompts } from '../../features/prompt/index.js';
|
||||
import { showCatalog } from '../../features/catalog/index.js';
|
||||
import { computeReviewMetrics, formatReviewMetrics, parseSinceDuration, purgeOldEvents } from '../../features/analytics/index.js';
|
||||
@ -127,6 +136,13 @@ program
|
||||
await deploySkill();
|
||||
});
|
||||
|
||||
program
|
||||
.command('export-codex')
|
||||
.description('Export takt pieces/agents as Codex Skill (~/.agents/)')
|
||||
.action(async () => {
|
||||
await deploySkillCodex();
|
||||
});
|
||||
|
||||
program
|
||||
.command('catalog')
|
||||
.description('List available facets (personas, policies, knowledge, instructions, output-contracts)')
|
||||
|
||||
@ -1,199 +1,19 @@
|
||||
/**
|
||||
* takt export-cc — Deploy takt skill files to Claude Code.
|
||||
*
|
||||
* Copies the following to ~/.claude/skills/takt/:
|
||||
* SKILL.md — Engine overview (user-invocable as /takt)
|
||||
* references/ — Engine logic + YAML schema
|
||||
* pieces/ — Builtin piece YAML files
|
||||
* personas/ — Builtin persona .md files
|
||||
* policies/ — Builtin policy files
|
||||
* instructions/ — Builtin instruction files
|
||||
* knowledge/ — Builtin knowledge files
|
||||
* output-contracts/ — Builtin output contract files
|
||||
* templates/ — Builtin template files
|
||||
*
|
||||
* Piece YAML persona paths (../personas/...) work as-is because
|
||||
* the directory structure is mirrored.
|
||||
*/
|
||||
|
||||
import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync, rmSync, statSync } from 'node:fs';
|
||||
import { homedir } from 'node:os';
|
||||
import { join, dirname, relative } from 'node:path';
|
||||
import { deploySkillInternal } from './deploySkillInternal.js';
|
||||
|
||||
import { getLanguage } from '../../infra/config/index.js';
|
||||
import { getResourcesDir, getLanguageResourcesDir } from '../../infra/resources/index.js';
|
||||
import { confirm } from '../../shared/prompt/index.js';
|
||||
import { header, success, info, warn, blankLine } from '../../shared/ui/index.js';
|
||||
|
||||
/** Files to skip during directory copy */
|
||||
const SKIP_FILES = new Set(['.DS_Store', 'Thumbs.db']);
|
||||
|
||||
/** Target paths under ~/.claude/ */
|
||||
function getSkillDir(): string {
|
||||
return join(homedir(), '.claude', 'skills', 'takt');
|
||||
}
|
||||
|
||||
/** Directories directly under builtins/{lang}/ */
|
||||
const DIRECT_DIRS = ['pieces', 'templates'] as const;
|
||||
|
||||
/** Facet directories under builtins/{lang}/facets/ */
|
||||
const FACET_DIRS = ['personas', 'policies', 'instructions', 'knowledge', 'output-contracts'] as const;
|
||||
|
||||
|
||||
/**
|
||||
* Deploy takt skill to Claude Code (~/.claude/).
|
||||
*/
|
||||
export async function deploySkill(): Promise<void> {
|
||||
header('takt export-cc — Deploy to Claude Code');
|
||||
|
||||
const lang = getLanguage();
|
||||
const skillResourcesDir = join(getResourcesDir(), 'skill');
|
||||
const langResourcesDir = getLanguageResourcesDir(lang);
|
||||
const skillDir = getSkillDir();
|
||||
|
||||
// Verify source directories exist
|
||||
if (!existsSync(skillResourcesDir)) {
|
||||
warn('Skill resources not found. Ensure takt is installed correctly.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if skill already exists and ask for confirmation
|
||||
const skillExists = existsSync(join(skillDir, 'SKILL.md'));
|
||||
if (skillExists) {
|
||||
info('Claude Code Skill が既にインストールされています。');
|
||||
const overwrite = await confirm(
|
||||
'既存のスキルファイルをすべて削除し、最新版に置き換えます。続行しますか?',
|
||||
false,
|
||||
);
|
||||
if (!overwrite) {
|
||||
info('キャンセルしました。');
|
||||
return;
|
||||
}
|
||||
blankLine();
|
||||
}
|
||||
|
||||
const copiedFiles: string[] = [];
|
||||
|
||||
// 1. Deploy SKILL.md
|
||||
const skillSrc = join(skillResourcesDir, 'SKILL.md');
|
||||
const skillDest = join(skillDir, 'SKILL.md');
|
||||
copyFile(skillSrc, skillDest, copiedFiles);
|
||||
|
||||
// 2. Deploy references/ (engine.md, yaml-schema.md)
|
||||
const refsSrcDir = join(skillResourcesDir, 'references');
|
||||
const refsDestDir = join(skillDir, 'references');
|
||||
cleanDir(refsDestDir);
|
||||
copyDirRecursive(refsSrcDir, refsDestDir, copiedFiles);
|
||||
|
||||
// 3. Deploy direct resource directories from builtins/{lang}/
|
||||
for (const dir of DIRECT_DIRS) {
|
||||
const srcDir = join(langResourcesDir, dir);
|
||||
const destDir = join(skillDir, dir);
|
||||
cleanDir(destDir);
|
||||
copyDirRecursive(srcDir, destDir, copiedFiles);
|
||||
}
|
||||
|
||||
// 4. Deploy facet directories from builtins/{lang}/facets/ (preserving facets/ structure)
|
||||
const facetsDestDir = join(skillDir, 'facets');
|
||||
cleanDir(facetsDestDir);
|
||||
for (const dir of FACET_DIRS) {
|
||||
const srcDir = join(langResourcesDir, 'facets', dir);
|
||||
const destDir = join(facetsDestDir, dir);
|
||||
copyDirRecursive(srcDir, destDir, copiedFiles);
|
||||
}
|
||||
|
||||
// Report results
|
||||
blankLine();
|
||||
if (copiedFiles.length > 0) {
|
||||
success(`${copiedFiles.length} ファイルをデプロイしました。`);
|
||||
blankLine();
|
||||
|
||||
// Show summary by category
|
||||
const skillBase = join(homedir(), '.claude');
|
||||
const skillFiles = copiedFiles.filter(
|
||||
(f) =>
|
||||
f.startsWith(skillDir) &&
|
||||
!f.includes('/pieces/') &&
|
||||
!f.includes('/facets/') &&
|
||||
!f.includes('/templates/') &&
|
||||
!f.includes('/references/'),
|
||||
);
|
||||
const pieceFiles = copiedFiles.filter((f) => f.includes('/pieces/'));
|
||||
const personaFiles = copiedFiles.filter((f) => f.includes('/facets/personas/'));
|
||||
const policyFiles = copiedFiles.filter((f) => f.includes('/facets/policies/'));
|
||||
const instructionFiles = copiedFiles.filter((f) => f.includes('/facets/instructions/'));
|
||||
const knowledgeFiles = copiedFiles.filter((f) => f.includes('/facets/knowledge/'));
|
||||
const outputContractFiles = copiedFiles.filter((f) => f.includes('/facets/output-contracts/'));
|
||||
const templateFiles = copiedFiles.filter((f) => f.includes('/templates/'));
|
||||
|
||||
if (skillFiles.length > 0) {
|
||||
info(` スキル: ${skillFiles.length} ファイル`);
|
||||
for (const f of skillFiles) {
|
||||
info(` ${relative(skillBase, f)}`);
|
||||
}
|
||||
}
|
||||
if (pieceFiles.length > 0) {
|
||||
info(` ピース: ${pieceFiles.length} ファイル`);
|
||||
}
|
||||
if (personaFiles.length > 0) {
|
||||
info(` ペルソナ: ${personaFiles.length} ファイル`);
|
||||
}
|
||||
if (policyFiles.length > 0) {
|
||||
info(` ポリシー: ${policyFiles.length} ファイル`);
|
||||
}
|
||||
if (instructionFiles.length > 0) {
|
||||
info(` インストラクション: ${instructionFiles.length} ファイル`);
|
||||
}
|
||||
if (knowledgeFiles.length > 0) {
|
||||
info(` ナレッジ: ${knowledgeFiles.length} ファイル`);
|
||||
}
|
||||
if (outputContractFiles.length > 0) {
|
||||
info(` 出力契約: ${outputContractFiles.length} ファイル`);
|
||||
}
|
||||
if (templateFiles.length > 0) {
|
||||
info(` テンプレート: ${templateFiles.length} ファイル`);
|
||||
}
|
||||
|
||||
blankLine();
|
||||
info('使い方: /takt <piece-name> <task>');
|
||||
info('例: /takt passthrough "Hello World テスト"');
|
||||
} else {
|
||||
info('デプロイするファイルがありませんでした。');
|
||||
}
|
||||
}
|
||||
|
||||
/** Remove a directory and all its contents so stale files don't persist across deploys. */
|
||||
function cleanDir(dir: string): void {
|
||||
if (existsSync(dir)) {
|
||||
rmSync(dir, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
/** Copy a single file, creating parent directories as needed. */
|
||||
function copyFile(src: string, dest: string, copiedFiles: string[]): void {
|
||||
if (!existsSync(src)) return;
|
||||
mkdirSync(dirname(dest), { recursive: true });
|
||||
writeFileSync(dest, readFileSync(src));
|
||||
copiedFiles.push(dest);
|
||||
}
|
||||
|
||||
/** Recursively copy directory contents, always overwriting. */
|
||||
function copyDirRecursive(srcDir: string, destDir: string, copiedFiles: string[]): void {
|
||||
if (!existsSync(srcDir)) return;
|
||||
mkdirSync(destDir, { recursive: true });
|
||||
|
||||
for (const entry of readdirSync(srcDir)) {
|
||||
if (SKIP_FILES.has(entry)) continue;
|
||||
|
||||
const srcPath = join(srcDir, entry);
|
||||
const destPath = join(destDir, entry);
|
||||
const stat = statSync(srcPath);
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
copyDirRecursive(srcPath, destPath, copiedFiles);
|
||||
} else {
|
||||
writeFileSync(destPath, readFileSync(srcPath));
|
||||
copiedFiles.push(destPath);
|
||||
}
|
||||
}
|
||||
await deploySkillInternal({
|
||||
headerTitle: 'takt export-cc — Deploy to Claude Code',
|
||||
skillRootDir: '.claude',
|
||||
skillResourceDirName: 'skill',
|
||||
existingInstallMessage: 'Claude Code Skill が既にインストールされています。',
|
||||
usageCommand: '使い方: /takt <piece-name> <task>',
|
||||
usageExample: '例: /takt passthrough "Hello World テスト"',
|
||||
showReferencesSummary: false,
|
||||
includeAgentsDirectory: false,
|
||||
showAgentsSummary: false,
|
||||
});
|
||||
}
|
||||
|
||||
19
src/features/config/deploySkillCodex.ts
Normal file
19
src/features/config/deploySkillCodex.ts
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* takt export-codex — Deploy takt skill files to Codex.
|
||||
*/
|
||||
|
||||
import { deploySkillInternal } from './deploySkillInternal.js';
|
||||
|
||||
export async function deploySkillCodex(): Promise<void> {
|
||||
await deploySkillInternal({
|
||||
headerTitle: 'takt export-codex — Deploy to Codex',
|
||||
skillRootDir: '.agents',
|
||||
skillResourceDirName: 'skill-codex',
|
||||
existingInstallMessage: 'Codex Skill が既にインストールされています。',
|
||||
usageCommand: '使い方: $takt <piece-name> <task>',
|
||||
usageExample: '例: $takt passthrough "Hello World テスト"',
|
||||
showReferencesSummary: true,
|
||||
includeAgentsDirectory: true,
|
||||
showAgentsSummary: true,
|
||||
});
|
||||
}
|
||||
187
src/features/config/deploySkillInternal.ts
Normal file
187
src/features/config/deploySkillInternal.ts
Normal file
@ -0,0 +1,187 @@
|
||||
import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync, rmSync, statSync } from 'node:fs';
|
||||
import { homedir } from 'node:os';
|
||||
import { join, dirname, relative } from 'node:path';
|
||||
|
||||
import { getLanguage } from '../../infra/config/index.js';
|
||||
import { getResourcesDir, getLanguageResourcesDir } from '../../infra/resources/index.js';
|
||||
import { confirm } from '../../shared/prompt/index.js';
|
||||
import { header, success, info, warn, blankLine } from '../../shared/ui/index.js';
|
||||
|
||||
const SKIP_FILES = new Set(['.DS_Store', 'Thumbs.db']);
|
||||
const DIRECT_DIRS = ['pieces', 'templates'] as const;
|
||||
const FACET_DIRS = ['personas', 'policies', 'instructions', 'knowledge', 'output-contracts'] as const;
|
||||
|
||||
export type DeploySkillOptions = {
|
||||
headerTitle: string;
|
||||
skillRootDir: string;
|
||||
skillResourceDirName: string;
|
||||
existingInstallMessage: string;
|
||||
usageCommand: string;
|
||||
usageExample: string;
|
||||
showReferencesSummary: boolean;
|
||||
includeAgentsDirectory: boolean;
|
||||
showAgentsSummary: boolean;
|
||||
};
|
||||
|
||||
export async function deploySkillInternal(options: DeploySkillOptions): Promise<void> {
|
||||
header(options.headerTitle);
|
||||
|
||||
const lang = getLanguage();
|
||||
const skillResourcesDir = join(getResourcesDir(), options.skillResourceDirName);
|
||||
const langResourcesDir = getLanguageResourcesDir(lang);
|
||||
const skillDir = join(homedir(), options.skillRootDir, 'skills', 'takt');
|
||||
|
||||
if (!existsSync(skillResourcesDir)) {
|
||||
warn('Skill resources not found. Ensure takt is installed correctly.');
|
||||
return;
|
||||
}
|
||||
|
||||
const skillExists = existsSync(join(skillDir, 'SKILL.md'));
|
||||
if (skillExists) {
|
||||
info(options.existingInstallMessage);
|
||||
const overwrite = await confirm(
|
||||
'既存のスキルファイルをすべて削除し、最新版に置き換えます。続行しますか?',
|
||||
false,
|
||||
);
|
||||
if (!overwrite) {
|
||||
info('キャンセルしました。');
|
||||
return;
|
||||
}
|
||||
blankLine();
|
||||
}
|
||||
|
||||
const copiedFiles: string[] = [];
|
||||
|
||||
copyFile(join(skillResourcesDir, 'SKILL.md'), join(skillDir, 'SKILL.md'), copiedFiles);
|
||||
|
||||
const referencesDestDir = join(skillDir, 'references');
|
||||
cleanDir(referencesDestDir);
|
||||
copyDirRecursive(join(skillResourcesDir, 'references'), referencesDestDir, copiedFiles);
|
||||
|
||||
if (options.includeAgentsDirectory) {
|
||||
const agentsDestDir = join(skillDir, 'agents');
|
||||
cleanDir(agentsDestDir);
|
||||
copyDirRecursive(join(skillResourcesDir, 'agents'), agentsDestDir, copiedFiles);
|
||||
}
|
||||
|
||||
for (const dir of DIRECT_DIRS) {
|
||||
const destDir = join(skillDir, dir);
|
||||
cleanDir(destDir);
|
||||
copyDirRecursive(join(langResourcesDir, dir), destDir, copiedFiles);
|
||||
}
|
||||
|
||||
const facetsDestDir = join(skillDir, 'facets');
|
||||
cleanDir(facetsDestDir);
|
||||
for (const dir of FACET_DIRS) {
|
||||
copyDirRecursive(join(langResourcesDir, 'facets', dir), join(facetsDestDir, dir), copiedFiles);
|
||||
}
|
||||
|
||||
blankLine();
|
||||
if (copiedFiles.length === 0) {
|
||||
info('デプロイするファイルがありませんでした。');
|
||||
return;
|
||||
}
|
||||
|
||||
success(`${copiedFiles.length} ファイルをデプロイしました。`);
|
||||
blankLine();
|
||||
|
||||
const skillBase = join(homedir(), options.skillRootDir);
|
||||
const skillFiles = copiedFiles.filter(
|
||||
(filePath) =>
|
||||
filePath.startsWith(skillDir)
|
||||
&& !filePath.includes('/pieces/')
|
||||
&& !filePath.includes('/facets/')
|
||||
&& !filePath.includes('/templates/')
|
||||
&& !filePath.includes('/references/')
|
||||
&& !filePath.includes('/agents/'),
|
||||
);
|
||||
const referenceFiles = copiedFiles.filter((filePath) => filePath.includes('/references/'));
|
||||
const agentFiles = copiedFiles.filter((filePath) => filePath.includes('/agents/'));
|
||||
const pieceFiles = copiedFiles.filter((filePath) => filePath.includes('/pieces/'));
|
||||
const personaFiles = copiedFiles.filter((filePath) => filePath.includes('/facets/personas/'));
|
||||
const policyFiles = copiedFiles.filter((filePath) => filePath.includes('/facets/policies/'));
|
||||
const instructionFiles = copiedFiles.filter((filePath) => filePath.includes('/facets/instructions/'));
|
||||
const knowledgeFiles = copiedFiles.filter((filePath) => filePath.includes('/facets/knowledge/'));
|
||||
const outputContractFiles = copiedFiles.filter((filePath) => filePath.includes('/facets/output-contracts/'));
|
||||
const templateFiles = copiedFiles.filter((filePath) => filePath.includes('/templates/'));
|
||||
|
||||
if (skillFiles.length > 0) {
|
||||
info(` スキル: ${skillFiles.length} ファイル`);
|
||||
for (const filePath of skillFiles) {
|
||||
info(` ${relative(skillBase, filePath)}`);
|
||||
}
|
||||
}
|
||||
if (options.showReferencesSummary && referenceFiles.length > 0) {
|
||||
info(` 参照資料: ${referenceFiles.length} ファイル`);
|
||||
}
|
||||
if (options.showAgentsSummary && agentFiles.length > 0) {
|
||||
info(` エージェント設定: ${agentFiles.length} ファイル`);
|
||||
}
|
||||
if (pieceFiles.length > 0) {
|
||||
info(` ピース: ${pieceFiles.length} ファイル`);
|
||||
}
|
||||
if (personaFiles.length > 0) {
|
||||
info(` ペルソナ: ${personaFiles.length} ファイル`);
|
||||
}
|
||||
if (policyFiles.length > 0) {
|
||||
info(` ポリシー: ${policyFiles.length} ファイル`);
|
||||
}
|
||||
if (instructionFiles.length > 0) {
|
||||
info(` インストラクション: ${instructionFiles.length} ファイル`);
|
||||
}
|
||||
if (knowledgeFiles.length > 0) {
|
||||
info(` ナレッジ: ${knowledgeFiles.length} ファイル`);
|
||||
}
|
||||
if (outputContractFiles.length > 0) {
|
||||
info(` 出力契約: ${outputContractFiles.length} ファイル`);
|
||||
}
|
||||
if (templateFiles.length > 0) {
|
||||
info(` テンプレート: ${templateFiles.length} ファイル`);
|
||||
}
|
||||
|
||||
blankLine();
|
||||
info(options.usageCommand);
|
||||
info(options.usageExample);
|
||||
}
|
||||
|
||||
function cleanDir(dir: string): void {
|
||||
if (existsSync(dir)) {
|
||||
rmSync(dir, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
function copyFile(src: string, dest: string, copiedFiles: string[]): void {
|
||||
if (!existsSync(src)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mkdirSync(dirname(dest), { recursive: true });
|
||||
writeFileSync(dest, readFileSync(src));
|
||||
copiedFiles.push(dest);
|
||||
}
|
||||
|
||||
function copyDirRecursive(srcDir: string, destDir: string, copiedFiles: string[]): void {
|
||||
if (!existsSync(srcDir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mkdirSync(destDir, { recursive: true });
|
||||
|
||||
for (const entry of readdirSync(srcDir)) {
|
||||
if (SKIP_FILES.has(entry)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const srcPath = join(srcDir, entry);
|
||||
const destPath = join(destDir, entry);
|
||||
const stat = statSync(srcPath);
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
copyDirRecursive(srcPath, destPath, copiedFiles);
|
||||
continue;
|
||||
}
|
||||
|
||||
writeFileSync(destPath, readFileSync(srcPath));
|
||||
copiedFiles.push(destPath);
|
||||
}
|
||||
}
|
||||
@ -6,3 +6,4 @@ export { ejectBuiltin, ejectFacet, parseFacetType, VALID_FACET_TYPES } from './e
|
||||
export { resetCategoriesToDefault } from './resetCategories.js';
|
||||
export { resetConfigToDefault } from './resetConfig.js';
|
||||
export { deploySkill } from './deploySkill.js';
|
||||
export { deploySkillCodex } from './deploySkillCodex.js';
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user