@shinaps/skills 0.37.0 → 0.39.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.
Files changed (38) hide show
  1. package/README.md +38 -23
  2. package/dist/bin/skills.js +4 -1
  3. package/dist/lib/config-validation.js +2 -1
  4. package/dist/lib/install.js +1 -0
  5. package/dist/lib/layout.js +12 -0
  6. package/dist/lib/linear-config.js +2 -2
  7. package/dist/lib/managed-ant.js +70 -0
  8. package/dist/lib/managed-config.js +6 -6
  9. package/dist/lib/managed-session.js +274 -0
  10. package/dist/lib/managed-setup.js +167 -0
  11. package/dist/lib/managed.js +249 -0
  12. package/package.json +1 -1
  13. package/plugins/shinaps-claude/.claude-plugin/plugin.json +1 -1
  14. package/plugins/shinaps-claude/managed-agents/agents/shinaps-dev-managed-cc.md +1 -1
  15. package/plugins/shinaps-claude/references/dev/launch.md +1 -1
  16. package/plugins/shinaps-claude/references/linear/claude-cli.md +14 -8
  17. package/plugins/shinaps-claude/references/linear/configuration.md +10 -7
  18. package/plugins/shinaps-claude/references/linear/configuration.schema.json +2 -1
  19. package/plugins/shinaps-claude/references/linear/workflow.md +1 -1
  20. package/plugins/shinaps-claude/references/managed/configuration.md +55 -30
  21. package/plugins/shinaps-claude/references/managed/configuration.schema.json +27 -17
  22. package/plugins/shinaps-claude/references/managed/execute.md +38 -74
  23. package/plugins/shinaps-claude/references/managed/setup.md +27 -39
  24. package/plugins/shinaps-claude/skills/managed-execute/SKILL.md +6 -6
  25. package/plugins/shinaps-claude/skills/managed-setup/SKILL.md +6 -7
  26. package/plugins/shinaps-codex/.codex-plugin/plugin.json +1 -1
  27. package/plugins/shinaps-codex/managed-agents/agents/shinaps-dev-managed-cc.md +1 -1
  28. package/plugins/shinaps-codex/references/linear/claude-cli.md +14 -8
  29. package/plugins/shinaps-codex/references/linear/configuration.md +10 -7
  30. package/plugins/shinaps-codex/references/linear/configuration.schema.json +2 -1
  31. package/plugins/shinaps-codex/references/linear/workflow.md +1 -1
  32. package/plugins/shinaps-codex/references/managed/configuration.md +55 -30
  33. package/plugins/shinaps-codex/references/managed/configuration.schema.json +27 -17
  34. package/plugins/shinaps-codex/references/managed/execute.md +38 -74
  35. package/plugins/shinaps-codex/references/managed/setup.md +27 -39
  36. package/plugins/shinaps-codex/skills/linear-execute/SKILL.md +1 -1
  37. package/plugins/shinaps-codex/skills/managed-execute/SKILL.md +6 -6
  38. package/plugins/shinaps-codex/skills/managed-setup/SKILL.md +6 -7
package/README.md CHANGED
@@ -18,10 +18,12 @@ sharedで採用する `instructions.md` は共有JSONの `references.specificati
18
18
 
19
19
  0.35.0から通常設定の `review_flow.mode` に `ask` を追加しました(パス別ルールは `auto`・`manual` だけ)。既存の設定は変更不要です。
20
20
 
21
- CLIはpluginに含まれないため、利用中のskillと同じ `@shinaps/skills@0.37.0` を導入し、`shinaps-skills --version` で確認します。`shinaps-skills validate-linear-config <明示ファイル>` は同梱schemaだけで検証し、ネットワーク取得・正本探索・Git操作・入力変更は行いません。`$schema` は固定URLによるエディタ補助で、契約の正本は[同梱schema](plugins/shinaps-claude/references/linear/configuration.schema.json)です。0.34.0から、Managed Agentsの `.shinaps/managed.json`(`config_version: 1`)も `shinaps-skills validate-managed-config <明示ファイル>` で同じ方式で検証します([Managedの設定](plugins/shinaps-claude/references/managed/configuration.md))。
21
+ 0.39.0からauto経路のClaude CLIは、起動とresumeの両方を `--permission-mode bypassPermissions` で行います。bypassへの継続的な同意は許可 `claude_cli_bypass_permissions` で表し、`claude_cli` だけの既存設定では、Codexが起動前に人へ確認します([Claude CLIの操作権限](plugins/shinaps-claude/references/linear/claude-cli.md#操作権限))。この許可を使う設定では `$schema` も0.39.0以降のURLにします。
22
+
23
+ CLIはpluginに含まれないため、利用中のskillと同じ `@shinaps/skills@0.39.0` を導入し、`shinaps-skills --version` で確認します。`shinaps-skills validate-linear-config <明示ファイル>` は同梱schemaだけで検証し、ネットワーク取得・正本探索・Git操作・入力変更は行いません。`$schema` は固定URLによるエディタ補助で、契約の正本は[同梱schema](plugins/shinaps-claude/references/linear/configuration.schema.json)です。Managed Agentsの `.shinaps/managed.json` も `shinaps-skills validate-managed-config <明示ファイル>` で同じ方式で検証します。0.38.0から `config_version: 2`(リポジトリとアカウント別のvault)になり、lockはアカウントごとの管理ディレクトリへ移ります([Managedの設定と移行](plugins/shinaps-claude/references/managed/configuration.md))。
22
24
 
23
25
  ```sh
24
- npm install -g @shinaps/skills@0.37.0
26
+ npm install -g @shinaps/skills@0.39.0
25
27
  shinaps-skills --version
26
28
  shinaps-skills validate-linear-config /absolute/repository/.shinaps/linear.json
27
29
  shinaps-skills validate-managed-config /absolute/repository/.shinaps/managed.json
@@ -44,12 +46,12 @@ shinaps-skills validate-managed-config /absolute/repository/.shinaps/managed.jso
44
46
  | `release` | 公開条件を確定し、対象バージョンの利用可能性を確認する |
45
47
  | `linear-plan` | 相談・必要なspec/explore・Issue作成と整理・プロジェクト設定を行い、実装前で止める |
46
48
  | `linear-execute` | Issue単体・Cycle全体・バックログ消化を選び、設定に従って実装・QA・許可された公開を進める |
47
- | `managed-setup` | Claude Managed Agentsの共通dev構成を自分の組織へ導入・更新する。既存のlockを使い、重複して作らない |
48
- | `managed-execute` | Managed Agentsのleadへdevを依頼し、観測・質問の取り次ぎ・成果物とdraft PRの確認を行う |
49
+ | `managed-setup` | Claude Managed Agentsの共通dev構成を1つ以上のアカウントへ一コマンドで導入・更新する。アカウントごとのlockを使い、重複して作らない |
50
+ | `managed-execute` | Managed Agentsのleadへdevを依頼し、観測・質問の取り次ぎ・出力のローカル回収とdraft PRの確認を行う |
49
51
 
50
52
  コミット・PR・リリースは、利用者の依頼またはLinear進行で合意済みの継続許可に従う。[共通設定](plugins/shinaps-claude/references/linear/configuration.md)・[担当と工程](plugins/shinaps-claude/references/linear/workflow.md)・[終了と待機](plugins/shinaps-claude/references/linear/execution.md)を参照し、必要な前工程が揃えば再確認せずに進む。単独スキル利用にはLinear進行や新しいゴールを必須にしない。`autonomous-commit-review` の役割は `commit` に統合した。個人用の別AI呼び出し・ブラウザ管理・ゴール管理・動画制作はこのpluginに含めない。
51
53
 
52
- `linear-execute` は初版では利用者本人の環境向けで、Codexが進行役として進行・Agent QA・Issueの記録・人への窓口を担当し、Claude Codeが実装・修正・コミットを行う。主フローは着手・実装・差分確定・公開確認・結果報告の5工程。運用項目は明示必須で、未記載を既定値で補わない。経路は着手時に通常の `review_flow.mode` で決まり、`ask` ではIssueごとに着手時の人の回答で決まる。autoはCodexがローカル `claude -p` を起動して同じsessionをresumeし(許可 `claude_cli`、[CLI手順](plugins/shinaps-claude/references/linear/claude-cli.md))、CLIの既存権限・effort・pluginsを保持する。メインのモデルは経路で分ける([経路](plugins/shinaps-claude/references/linear/configuration.md#経路))。manualはWebStorm内の対話Claude CodeセッションへBridgeで依頼する(許可 `bridge_request`、[Bridge](plugins/shinaps-claude/references/linear/bridge.md))。manualではQA後もCodexはcommitを依頼せず、人の依頼でClaudeがcommitする。体制はCodexが確認して依頼に書き、BASIC・ULTRAでは追加の担当をClaudeのメインが判断の材料と現物から決めて報告する。Bridge本体は同梱せず、導入済みのMCPとWebStormプラグインを使う。`SKILL.md` は担当ごとに両版で書き分け、共通の手順と書式は `references/linear/` に置く。`references/linear/` はClaude版を編集元としてCodex版へ同じ内容を複製し、テストで一致を確認する。
54
+ `linear-execute` は初版では利用者本人の環境向けで、Codexが進行役として進行・Agent QA・Issueの記録・人への窓口を担当し、Claude Codeが実装・修正・コミットを行う。主フローは着手・実装・差分確定・公開確認・結果報告の5工程。運用項目は明示必須で、未記載を既定値で補わない。経路は着手時に通常の `review_flow.mode` で決まり、`ask` ではIssueごとに着手時の人の回答で決まる。autoはCodexがローカル `claude -p` を起動して同じsessionをresumeし(許可 `claude_cli`・`claude_cli_bypass_permissions`、[CLI手順](plugins/shinaps-claude/references/linear/claude-cli.md))、起動とresumeの両方で `--permission-mode bypassPermissions` を指定し、effort・pluginsは保持する。メインのモデルは経路で分ける([経路](plugins/shinaps-claude/references/linear/configuration.md#経路))。manualはWebStorm内の対話Claude CodeセッションへBridgeで依頼する(許可 `bridge_request`、[Bridge](plugins/shinaps-claude/references/linear/bridge.md))。manualではQA後もCodexはcommitを依頼せず、人の依頼でClaudeがcommitする。体制はCodexが確認して依頼に書き、BASIC・ULTRAでは追加の担当をClaudeのメインが判断の材料と現物から決めて報告する。Bridge本体は同梱せず、導入済みのMCPとWebStormプラグインを使う。`SKILL.md` は担当ごとに両版で書き分け、共通の手順と書式は `references/linear/` に置く。`references/linear/` はClaude版を編集元としてCodex版へ同じ内容を複製し、テストで一致を確認する。
53
55
 
54
56
  `review_flow: { "mode": "auto" }` はAIが自然な単位を決め、独立レビュー・QA後にstage・commitする。`review_flow: { "mode": "manual" }` はAIがレビュー待ちを明示し、人がWebStormで差分を確認する。人のコミット開始依頼でAIが分割案を示し、人がその単位のコミットを依頼したらAIが単位外を安全に退避して単位をstage・検証・commitする。`review_flow: { "mode": "ask" }` は着手時に進行役が作業内容と推奨理由を添えてautoかmanualかを一問で確認し、回答を同じIssueの通常flowとして使う。パス別ルールには書けない。各modeのstage・AI commit権限は利用者との明示合意に含まれ、未合意ファイルからは得ない。`path_policy_overrides` は `paths` と `review_flow` だけを持つ。全変更が一つのルールで覆われる単位だけ適用し、改名は旧新両パスを判定する。競合時は止める。旧 `human_code_review`・`commit_mode`・permissionsのstage/commit・パス別ai_commitは明示移行が必要。[共通設定](plugins/shinaps-claude/references/linear/configuration.md#パス別の方針)を参照する。
55
57
 
@@ -134,38 +136,50 @@ Claude Codeは標準のmarketplace自動更新機能を使う。`/plugin` のMar
134
136
 
135
137
  ## Claude Managed Agents
136
138
 
137
- 両pluginの `managed-agents/` は、Claude Managed Agentsでdev工程を進めるための共通定義である。lead `shinaps-dev-managed-cc`、devの3役割、skill `dev-managed-cc`、環境 `shinaps-dev` を置く。クラウドのIDを記録する `claude-lock.json` と実設定は利用者ごとのものなので、作業先の `.shinaps/` に置き、公開する定義には含めない。予定実行はこの構成に含めない。
139
+ 両pluginの `managed-agents/` は、Claude Managed Agentsでdev工程を進めるための共通定義である。lead `shinaps-dev-managed-cc`、devの3役割、skill `dev-managed-cc`、環境 `shinaps-dev` を置く。クラウドのIDを記録する `claude-lock.json` は利用者のアカウント(APIキーの組織・workspace)ごとのものなので、マシン単位の管理ディレクトリに置き、公開する定義には含めない。予定実行はこの構成に含めない。
138
140
 
139
- - leadはFable 5.1(`claude-fable-5-1` / medium)、役割はOpus 5.5(`claude-opus-5-5` / medium)で動く。全agent定義に `speed: standard` を明示する(既存リソースに残るfastを戻すため)。leadのモデルは定義で決まり、Linear進行の経路ごとのメインのモデルとは別に扱う。
140
- - 体制はLITE・BASIC・ULTRAを初回メッセージで受け、`dev-claude` と同じ体制の表に従い、BASIC・ULTRAではleadが追加の担当を決める。leadがメインとして調査・設計・実装・採否を行い、独立設計・計画レビュー・実装後の独立レビューだけを登録した3役割(architect・plan-reviewer・reviewer)へ委任する。そのため Managed で設計・実装を行うモデルは lead の Fable になる(モデルの定義は変えない)。廃止したdev-ultraなどの入口とexplorerは置かず、探索はleadと独立設計担当が行う。
141
+ - leadと3役割はOpus 5.5(`claude-opus-5-5` / medium)で動く。全agent定義に `speed: standard` を明示する(既存リソースに残るfastを戻すため)。leadのモデルは定義で決まり、Linear進行の経路ごとのメインのモデルとは別に扱う。
142
+ - 体制はLITE・BASIC・ULTRAを初回メッセージで受け、`dev-claude` と同じ体制の表に従い、BASIC・ULTRAではleadが追加の担当を決める。leadがメインとして調査・設計・実装・採否を行い、独立設計(ULTRAでは必須)・計画レビュー・実装後の独立レビューだけを登録した3役割(architect・plan-reviewer・reviewer)へ委任する。Managedで設計・実装を行うモデルはleadのOpusになる。廃止したdev-ultraなどの入口・explorer・refine専用の役割は置かず、探索はleadと独立設計担当が行う。
141
143
  - Shinaps AIのMCPとruleを使わない。規約はマウントしたリポジトリの `AGENTS.md`・`CLAUDE.md` と参照先、添付された利用者CLAUDE.mdだけで、最終報告ではShinaps AI ruleを未照合とする。
142
- - 初回メッセージにリポジトリ・base・head・範囲と受入条件・許可・予算が揃わなければ、leadは作業を始めず欠落を質問する。許可はそのメッセージで明示された操作だけで、構成の導入からは生じない。
144
+ - 初回メッセージにリポジトリ・base・head・範囲と受入条件・体制・許可・予算が揃わなければ、leadは作業を始めず欠落を質問する。許可はそのメッセージで明示された操作だけで、構成の導入からは生じない。
143
145
  - 終了地点は範囲を限ったコミット・headブランチへのpush・draft PR。merge・deploy・release・Issueの更新はしない。CodexのQAはセッションの外で、PRの後に行う。push前に最終QAを行うLinear進行とは順序が違う。
144
146
 
145
- ### 定義の更新と適用
147
+ ### CLI
146
148
 
147
- 1. 共通定義は `plugins/shinaps-claude/managed-agents/` を編集元とし、`plugins/shinaps-codex/managed-agents/` へ同じ内容を複製する。一致はテストで確認する。
148
- 2. 作業先では `managed-setup` が、`.shinaps/managed.json`([設定](plugins/shinaps-claude/references/managed/configuration.md))の `definitions` が指すディレクトリ(既定は `.shinaps/managed/`、Git対象外)へ共通定義をコピーする。
149
- 3. そのディレクトリで `ant apply --dry-run .` を実行し、計画を確認する。
150
- 4. 利用者の合意後に `ant apply .` で適用する。`--prune`・`--force` は使わず、リソースを削除しない。
151
- 5. 適用前に複製したlock(`.shinaps/work/` に置く)と比べ、更新したリソースの `version`・`hash`・`remote_hash` 以外が変わっていないことを確かめる。
149
+ 導入・依頼・観測・回収は配布CLIの `shinaps-skills managed` で行い、クラウドの操作は公式の `ant` CLI(2.4.0以上。コマンド・フラグ・stdin・出力はこの版のソースで照合した)へ委ねる。各アカウントのAPIキーは環境変数 `SHINAPS_MANAGED_API_KEY_<NAME>` か引数 `--api-key <name>=<key>` で渡し、CLIは保存・表示しない。lock・セッション記録・回収した出力は `~/.local/share/shinaps-ai/skills/managed/<account>/` にアカウントごとに分かれる。
152
150
 
153
- 手順・安全上の注意・止める条件は[導入と更新の手順](plugins/shinaps-claude/references/managed/setup.md)にある。セッションの依頼・観測・未検証の境界は[依頼と観測の手順](plugins/shinaps-claude/references/managed/execute.md)にある。これらはpluginに含まれ、どちらのruntimeからも読める。
151
+ ```sh
152
+ shinaps-skills managed setup --account personal --account work # 計画の表示だけ
153
+ shinaps-skills managed setup --account personal --account work --apply # 合意後に適用
154
+ shinaps-skills managed run .shinaps/managed.json --account work --base main --base-sha <SHA> --head feat/x \
155
+ --staffing BASIC --allow commit,push,draft_pr --budget-cents 4000 --task-file task.md
156
+ shinaps-skills managed status --account work --session <ID>
157
+ shinaps-skills managed reply --account work --session <ID> --message-file answer.md
158
+ shinaps-skills managed collect --account work --session <ID>
159
+ ```
154
160
 
155
- ### このリポジトリのlock
161
+ - setupはアカウントごとのlockで既存のリソースを更新し、lockが無いのに同名のリソースがあれば作らずに止まる。`--apply` を付けない限りクラウドを変更しない。
162
+ - runはリポジトリ・base・head・範囲・体制・許可・予算を毎回明示し、同じ入力の再実行では新しいセッションを作らない。
163
+ - collectはセッションの出力を保存するだけで、差分を適用しない。CLIはgitを呼ばず、pushしない。
156
164
 
157
- shinaps-skills自身のlockは0.34.0で `.shinaps/managed/claude-lock.json`(Git対象外)へ移した。以前の内容は `git show 8679d8f:managed-agents/claude-lock.json` で取り出せる。
165
+ 手順・安全上の注意・止める条件は[設定とアカウント](plugins/shinaps-claude/references/managed/configuration.md)・[導入と更新の手順](plugins/shinaps-claude/references/managed/setup.md)・[依頼と観測の手順](plugins/shinaps-claude/references/managed/execute.md)にある。これらはpluginに含まれ、どちらのruntimeからも読める。
158
166
 
159
- 定義を変えたら、`managed-setup` の手順([導入と更新の手順](plugins/shinaps-claude/references/managed/setup.md))でdry-runの計画を確かめてから適用する。`--prune` は付けない。旧CC Ultraのleadと専用skill、旧explorerと旧review-verifierはクラウドでアーカイブ済みで、lockでも追跡しない。
167
+ ### 定義の更新と適用
160
168
 
161
- GitHub MCP(`https://api.githubcopilot.com/mcp`)の資格情報はvaultに登録し、セッション作成時に渡す。vaultの資格情報はMCPサーバーのURLで照合されるので、lead定義のURLと末尾のスラッシュまで揃える。leadは最後のメッセージの1行目に `状態: 完了`・`状態: 質問`・`状態: 未完了` を書き、`changes.patch` と作業記録のtarを `/mnt/session/outputs/` に置く([起動手順](plugins/shinaps-claude/managed-agents/skills/dev-managed-cc/references/launch.md))。マウントしたリポジトリへのpushとdraft PRの作成は、実際のセッションで確かめるまで未検証である。
169
+ 共通定義は `plugins/shinaps-claude/managed-agents/` を編集元とし、`plugins/shinaps-codex/managed-agents/` へ同じ内容を複製する。一致はテストで確認する。定義を変えたら `managed setup` で計画を確かめてから `--apply` で適用する。`--prune`・`--force` は使わず、リソースを削除しない。
170
+
171
+ ### このリポジトリのlock
172
+
173
+ shinaps-skills自身のlockは0.34.0から0.37.0まで `.shinaps/managed/claude-lock.json`(Git対象外)に置いていた。0.38.0では[version 1 からの移行](plugins/shinaps-claude/references/managed/configuration.md#version-1-からの移行)に従い、アカウントの `definitions/claude-lock.json` へ移してから適用する。それ以前の内容は `git show 8679d8f:managed-agents/claude-lock.json` で取り出せる。旧CC Ultraのleadと専用skill、旧explorerと旧review-verifierはクラウドでアーカイブ済みで、lockでも追跡しない。
174
+
175
+ GitHub MCP(`https://api.githubcopilot.com/mcp`)の資格情報はアカウントの組織のvaultに登録し、セッション作成時に渡す。vaultの資格情報はMCPサーバーのURLで照合されるので、lead定義のURLと末尾のスラッシュまで揃える。leadは最後のメッセージの1行目に `状態: 完了`・`状態: 質問`・`状態: 未完了` を書き、`changes.patch` と作業記録のtarを `/mnt/session/outputs/` に置く([起動手順](plugins/shinaps-claude/managed-agents/skills/dev-managed-cc/references/launch.md))。CLIの実クラウドでの実行、マウントしたリポジトリへのpushとdraft PRの作成は、実際のセッションで確かめるまで未検証である。
162
176
 
163
177
  ## 開発と配布
164
178
 
165
179
  - Claude版: `plugins/shinaps-claude/`、marketplaceは`.claude-plugin/marketplace.json`
166
180
  - Codex版: `plugins/shinaps-codex/`、marketplaceは`.agents/plugins/marketplace.json`
167
181
  - Codexカスタムエージェント: `plugins/shinaps-codex/agents/`
168
- - Claude Managed Agents版: 両pluginの `managed-agents/`(Claude版が編集元)。pluginと一緒に配布され、npmパッケージの `plugins/` にも含まれる。適用は作業先の `.shinaps/managed/` へコピーしてから「Claude Managed Agents」節の手順で行い、`--prune`・`--force` を使わない
182
+ - Claude Managed Agents版: 両pluginの `managed-agents/`(Claude版が編集元)。pluginと一緒に配布され、npmパッケージの `plugins/` にも含まれる。適用は `shinaps-skills managed setup` がアカウントごとの管理ディレクトリへコピーしてから行い、`--prune`・`--force` を使わない
169
183
  - 配布CLI: ソースは `bin/skills.ts` と `lib/*.ts`。`npm run build` (`tsc`) で `dist/` にJSを出力し、npmにはそれを配布する。Node.jsは`node_modules`配下の`.ts`に型除去を適用しないため、ソースのままは配布できない。checkoutでは型除去で直接実行するので、`enum`など型除去で消せない構文は使わない (`tsconfig.json` の `erasableSyntaxOnly`)
170
184
 
171
185
  品質基準とプロジェクト設定の方針は揃えつつ、クライアント固有の実行手順・エージェント形式は別々に管理する。Codex版の工程の資料(`references/` の、`dev/launch.md` を除くもの)はClaude版と一致させ、テストで確認する。Codex版は役割の本文とスキルを書き分け、Claude向けのエージェント呼び出しをCodexに読み替えさせる方式は使わない。スキルを追加・変更した場合は各版への適用可否を判断する。
@@ -254,8 +268,7 @@ Claude版は全14エージェントが `opus / medium`。Codex版の役割定義
254
268
  ├── .gitignore # 保存方式に応じた除外・共有対象の例外
255
269
  ├── linear.json # linear のプロジェクト設定(任意)
256
270
  ├── linear/ # linear の補足(任意)
257
- ├── managed.json # Managed Agents のローカル設定(任意・Git対象外)
258
- ├── managed/ # Managed の共通定義のコピーと claude-lock.json(任意・Git対象外)
271
+ ├── managed.json # Managed Agents の依頼先リポジトリとアカウント別のvault(任意・Git対象外)
259
272
  └── work/
260
273
  ├── {日時}-{作業名}/
261
274
  ├── explorations/
@@ -264,6 +277,8 @@ Claude版は全14エージェントが `opus / medium`。Codex版の役割定義
264
277
  └── debug/
265
278
  ```
266
279
 
280
+ Managed Agentsのアカウントごとのlock・セッション記録・回収した出力は、リポジトリではなく `~/.local/share/shinaps-ai/skills/managed/<account>/` に置く([設定とアカウント](plugins/shinaps-claude/references/managed/configuration.md))。
281
+
267
282
  旧 `.claude/shinaps/` の成果物はパスを指定して引き継げる。既存記録を自動移動・削除しない。共有する仕様書などの明示された納品先は尊重し、作業記録に秘密情報を含めない。
268
283
 
269
284
  ## 対応範囲
@@ -4,6 +4,7 @@ import { join } from 'node:path';
4
4
  import { packageRoot } from '../lib/layout.js';
5
5
  import { validateLinearConfig } from '../lib/linear-config.js';
6
6
  import { validateManagedConfig } from '../lib/managed-config.js';
7
+ import { managed } from '../lib/managed.js';
7
8
  import { run } from '../lib/install.js';
8
9
  import { autoUpdate } from '../lib/auto-update.js';
9
10
  import { errorMessage } from '../lib/fs.js';
@@ -22,8 +23,10 @@ try {
22
23
  if (rest.length !== 1)
23
24
  throw new Error('Usage: shinaps-skills validate-managed-config <file>');
24
25
  await validateManagedConfig(rest[0]);
25
- console.log('Managed configuration is valid (config_version: 1).');
26
+ console.log('Managed configuration is valid (config_version: 2).');
26
27
  }
28
+ else if (command === 'managed')
29
+ await managed(rest);
27
30
  else if (command === 'auto-update')
28
31
  await autoUpdate(rest);
29
32
  else
@@ -2,7 +2,7 @@ import { readFile } from 'node:fs/promises';
2
2
  import { join } from 'node:path';
3
3
  import { Ajv } from 'ajv';
4
4
  import { packageRoot } from './layout.js';
5
- /** 明示されたファイルを同梱の契約で検証する。設定の探索・変更や外部取得は行わない。 */
5
+ /** 明示されたファイルを同梱の契約で検証し、検証した内容を返す。設定の探索・変更や外部取得は行わない。 */
6
6
  export async function validateConfig(path, contract) {
7
7
  const input = JSON.parse(await readFile(path, 'utf8'));
8
8
  // 版の不一致は設定修正とskill更新で対処が異なるため、schema検証より先に案内する。
@@ -30,4 +30,5 @@ export async function validateConfig(path, contract) {
30
30
  .join('\n');
31
31
  throw new Error(`Invalid ${contract.name} configuration. Correct the JSON to match configuration.schema.json:\n${errors}`);
32
32
  }
33
+ return input;
33
34
  }
@@ -12,6 +12,7 @@ const usage = [
12
12
  'Usage: shinaps-skills <install|update|uninstall|recover> codex',
13
13
  ' shinaps-skills validate-linear-config <file>',
14
14
  ' shinaps-skills validate-managed-config <file>',
15
+ ' shinaps-skills managed <setup|run|status|reply|collect> (see managed --help)',
15
16
  ' shinaps-skills auto-update <enable|disable|status|check> codex',
16
17
  'Claude Code: /plugin marketplace add sh1n4ps/shinaps-skills',
17
18
  ' /plugin install shinaps@shinaps-skills',
@@ -5,6 +5,8 @@ import { fileURLToPath } from 'node:url';
5
5
  // launchd ジョブは enable した版の CLI ではなく、実行のたびに npm から取得した最新版の CLI を起動する。
6
6
  // そのためここに定義した場所と形式 (auto-update.json、state.json、transaction/journal.json) は
7
7
  // どの版でも読めるように保ち、変える場合は旧形式も読めるようにする。
8
+ // Managed Agents のアカウントごとの配置 (managed/<account>/ の lock とセッション記録) も同じ約束で、
9
+ // 版を変えても既存の lock を見失わないようにする。lock を見失うと、次の適用が同じリソースを重複して作る。
8
10
  /**
9
11
  * このパッケージのルートディレクトリ。リポジトリの checkout、または npm が展開したディレクトリ。
10
12
  * このファイルは checkout では `lib/`、配布物では `dist/lib/` にあり深さが違うので、package.json が
@@ -39,6 +41,16 @@ export const managedRoot = (home) => join(home, '.local', 'share', 'shinaps-ai',
39
41
  /** 配置済みの配布物。Codex の marketplace はここを指す。 */
40
42
  export const payloadDir = (root) => join(root, 'codex');
41
43
  export const statePath = (root) => join(root, 'state.json');
44
+ /**
45
+ * Managed Agents のアカウントのディレクトリ。lock は組織・workspace (APIキー) に属し、リポジトリには
46
+ * 属さないので、リポジトリではなくマシン単位の管理ディレクトリに置く。
47
+ * - `definitions/`: `ant apply` の cwd。同梱の共通定義の写しと `claude-lock.json` だけを置く。
48
+ * - `sessions/<request_id>.json`: このアカウントで作成したセッションの記録。
49
+ * - `sessions/<session_id>/outputs/`: 回収したファイル。
50
+ */
51
+ export const managedAccountDir = (root, account) => join(root, 'managed', account);
52
+ /** 同梱の Managed 共通定義。両 plugin の内容は同一 (テストで確認) なので Claude 版を使う。 */
53
+ export const managedTemplatePath = 'plugins/shinaps-claude/managed-agents';
42
54
  /** CODEX_HOME の解決値。オプション (`auto-update check` が渡す) → 環境変数 → Codex の既定の順。 */
43
55
  export const codexHomeOf = (options, home) => resolve(options.codexHome ?? process.env.CODEX_HOME ?? join(home, '.codex'));
44
56
  /**
@@ -1,7 +1,7 @@
1
1
  import { validateConfig } from './config-validation.js';
2
2
  /** `.shinaps/linear.json` を同梱の契約で検証する。 */
3
- export function validateLinearConfig(path) {
4
- return validateConfig(path, {
3
+ export async function validateLinearConfig(path) {
4
+ await validateConfig(path, {
5
5
  name: 'Linear',
6
6
  schema: 'plugins/shinaps-claude/references/linear/configuration.schema.json',
7
7
  version: 2,
@@ -0,0 +1,70 @@
1
+ import { spawn } from 'node:child_process';
2
+ /**
3
+ * Managed Agents の beta ヘッダ。ant の `--beta` は既定の beta ヘッダを置き換えるので、既定に頼らず
4
+ * 毎回明示する (agents・environments・sessions の既定と同じ値)。Files は `filesBeta` を使う。
5
+ */
6
+ export const managedBeta = ['--beta', 'managed-agents-2026-04-01'];
7
+ /** セッションの Files には Files API と Managed Agents の両方の beta が要る。 */
8
+ export const filesBeta = ['--beta', 'files-api-2025-04-14', ...managedBeta];
9
+ /**
10
+ * 子プロセスの環境。親の `ANTHROPIC_*` (別のキー・ログインのプロファイル・workspace・federation) と
11
+ * 他アカウントのキー (`SHINAPS_MANAGED_*`) を渡さず、そのアカウントのキーだけを資格情報にする。
12
+ * ant は環境変数をログインより優先するが、複数の資格情報が残ると、どれが使われるかが ant の優先順位に
13
+ * 依存し、別アカウントのリソースを操作しかねないため。
14
+ */
15
+ export function antEnvironment(env, apiKey) {
16
+ const result = {};
17
+ for (const [name, value] of Object.entries(env)) {
18
+ if (!name.startsWith('ANTHROPIC_') && !name.startsWith('SHINAPS_MANAGED_')) {
19
+ result[name] = value;
20
+ }
21
+ }
22
+ result.ANTHROPIC_API_KEY = apiKey;
23
+ // ant は FORCE_COLOR=1 で非端末にも色を付ける。計画の文言や jsonl の解析が ANSI で壊れないよう止める。
24
+ result.FORCE_COLOR = '0';
25
+ return result;
26
+ }
27
+ /**
28
+ * stdin に渡すリクエスト本文。ant は本文の文字列値のうち `@` で始まるものをファイル参照として展開し
29
+ * (`@notes.md` ならそのファイルの内容を送り、無ければ失敗する)、`\@` で始まるものだけを `@` の文字列に
30
+ * 戻す。題名や回答の本文を書かれたとおりに送るため、`@` で始まる文字列を全て `\@` に置き換える。
31
+ * `\@` で始まる文字列は ant が必ず `@` に変えるので、そのままは送れない (ant の制約)。
32
+ */
33
+ export function antRequestBody(value) {
34
+ return JSON.stringify(value, (_key, item) => typeof item === 'string' && item.startsWith('@') ? `\\${item}` : item);
35
+ }
36
+ /**
37
+ * PATH 上の `ant` を実行する。本文が無ければ stdin を /dev/null にする。ant は stdin がパイプなら
38
+ * 本文の有無を 10 ms 待って確かめるので、本文の無い呼び出しにパイプを渡さない。本文は起動と同時に
39
+ * パイプへ書き込むので、ant が確かめる時点では既に読める。
40
+ */
41
+ export const antCommand = (env) => ({ argv, apiKey, cwd, body }) => new Promise((resolve, reject) => {
42
+ const child = spawn('ant', argv, {
43
+ cwd,
44
+ env: antEnvironment(env, apiKey),
45
+ stdio: [body === undefined ? 'ignore' : 'pipe', 'pipe', 'pipe'],
46
+ });
47
+ const stdout = [];
48
+ const stderr = [];
49
+ child.stdout?.on('data', (chunk) => stdout.push(chunk));
50
+ child.stderr?.on('data', (chunk) => stderr.push(chunk));
51
+ child.on('error', reject);
52
+ child.on('close', (status, signal) => {
53
+ if (status === 0) {
54
+ resolve(Buffer.concat(stdout).toString('utf8'));
55
+ return;
56
+ }
57
+ // ant は API エラーの本文と、途中で失敗した apply の結果を stdout に出すので、両方を残す。
58
+ // 呼び出し側が秘密値を伏字にしてから表示する。
59
+ const output = [Buffer.concat(stdout), Buffer.concat(stderr)]
60
+ .map((chunk) => chunk.toString('utf8').trim())
61
+ .filter(Boolean)
62
+ .join('\n');
63
+ reject(new Error(`ant ${argv.slice(0, 2).join(' ')} exited with ${status ?? signal}:\n${output}`));
64
+ });
65
+ // ant が本文を読む前に終わる (引数の誤りなど) と書き込みは EPIPE で失敗する。その成否は
66
+ // 終了コードとエラー出力で報告するので、書き込みのエラーでプロセスを落とさない。
67
+ child.stdin?.on('error', () => { });
68
+ if (body !== undefined)
69
+ child.stdin?.end(antRequestBody(body));
70
+ });
@@ -1,10 +1,10 @@
1
1
  import { validateConfig } from './config-validation.js';
2
- /** `.shinaps/managed.json` を同梱の契約で検証する。 */
3
- export function validateManagedConfig(path) {
4
- return validateConfig(path, {
2
+ /** `.shinaps/managed.json` を同梱の契約で検証し、内容を返す。 */
3
+ export async function validateManagedConfig(path) {
4
+ return (await validateConfig(path, {
5
5
  name: 'Managed',
6
6
  schema: 'plugins/shinaps-claude/references/managed/configuration.schema.json',
7
- version: 1,
8
- migration: 'Create it with managed-setup (config_version: 1).',
9
- });
7
+ version: 2,
8
+ migration: 'Recreate it as config_version: 2 with managed-execute and move each existing claude-lock.json to its account directory (references/managed/configuration.md, migration from version 1).',
9
+ }));
10
10
  }
@@ -0,0 +1,274 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { mkdir, readdir, rename, rm, stat } from 'node:fs/promises';
3
+ import { join, resolve } from 'node:path';
4
+ import { readJson, readJsonIfExists, writeFileAtomic } from './fs.js';
5
+ import { withLock } from './lock.js';
6
+ import { filesBeta, managedBeta } from './managed-ant.js';
7
+ export const staffings = ['LITE', 'BASIC', 'ULTRA'];
8
+ export const permissions = ['commit', 'push', 'draft_pr'];
9
+ const leadKey = './agents/shinaps-dev-managed-cc.md';
10
+ const environmentKey = './environments/shinaps-dev.yaml';
11
+ const permissionLabels = {
12
+ commit: 'commit',
13
+ push: 'headへのpush',
14
+ draft_pr: 'draft PRの作成',
15
+ };
16
+ const sha256 = (text) => createHash('sha256').update(text).digest('hex');
17
+ /**
18
+ * `--transform id -r` の出力から ID を取り出す。ant は transform のパスが応答に無いと応答の全体を
19
+ * 出すので、ID の形でない出力を ID として記録しない。
20
+ */
21
+ async function readId(output, command) {
22
+ const id = (await output).trim();
23
+ if (!/^[A-Za-z0-9_]+$/.test(id))
24
+ throw new Error(`${command} returned no ID.`);
25
+ return id;
26
+ }
27
+ /**
28
+ * 依頼を lead へ送るセッションを作る。同じ入力の依頼は同じセッションを返し、作り直さない。
29
+ * 作成の途中で止まった記録があれば、重複を避けるために作らずに止める。
30
+ * @param githubToken マウントするリポジトリの token。本文 (stdin) だけで渡す。
31
+ * @param claudeMdPath `request.claude_md` の元のファイル。upload に使う。
32
+ */
33
+ export async function runSession(account, request, options) {
34
+ const { ant, log } = options;
35
+ return withLock(account.dir, async () => {
36
+ const { task, claude_md, ...rest } = request;
37
+ const recorded = {
38
+ ...rest,
39
+ task_sha256: sha256(task),
40
+ claude_md_sha256: claude_md === null ? null : sha256(claude_md),
41
+ };
42
+ const requestId = sha256(JSON.stringify(recorded)).slice(0, 32);
43
+ const recordPath = join(account.dir, 'sessions', `${requestId}.json`);
44
+ const previous = await readJsonIfExists(recordPath);
45
+ if (previous?.session_id) {
46
+ log(`Session already exists for this request: ${previous.session_id}`);
47
+ return previous.session_id;
48
+ }
49
+ if (previous) {
50
+ throw new Error(`A previous run of this request stopped while creating a session. Check the sessions of account ${account.name} for metadata shinaps_request=${requestId}; delete ${recordPath} only after confirming none was created, then retry.`);
51
+ }
52
+ const lock = await readJsonIfExists(join(account.dir, 'definitions', 'claude-lock.json'));
53
+ const agent = lock?.resources?.[leadKey]?.id;
54
+ const environment = lock?.resources?.[environmentKey]?.id;
55
+ if (!agent || !environment) {
56
+ throw new Error(`Account ${account.name} has no applied lead and environment. Run managed setup --apply first.`);
57
+ }
58
+ await mkdir(join(account.dir, 'sessions'), { recursive: true });
59
+ const record = {
60
+ request_id: requestId,
61
+ session_id: null,
62
+ created_at: options.now().toISOString(),
63
+ request: recorded,
64
+ };
65
+ // 作成より前に記録する。作成後に記録できずに終わっても、次の実行は止まり、重複して作らない。
66
+ await writeFileAtomic(recordPath, JSON.stringify(record, null, 2) + '\n');
67
+ const resources = [
68
+ {
69
+ type: 'github_repository',
70
+ url: request.repository.url,
71
+ mount_path: request.repository.mount_path,
72
+ ...(options.githubToken ? { authorization_token: options.githubToken } : {}),
73
+ checkout: { type: 'commit', sha: request.base_sha },
74
+ },
75
+ ];
76
+ if (options.claudeMdPath) {
77
+ // upload は記録の後に行う。記録の前だと、途中で止まった実行の再実行のたびに Files が増える。
78
+ let fileId;
79
+ try {
80
+ fileId = await readId(ant({
81
+ // セッションの Files と同じ beta の Files API に上げる。パスは絶対パスにする (ant は `-` を
82
+ // stdin と解釈する)。
83
+ argv: [
84
+ 'beta:files',
85
+ 'upload',
86
+ '--file',
87
+ resolve(options.claudeMdPath),
88
+ ...filesBeta,
89
+ '--transform',
90
+ 'id',
91
+ '-r',
92
+ ],
93
+ apiKey: account.apiKey,
94
+ }), 'ant files upload');
95
+ }
96
+ catch (error) {
97
+ // セッションはまだ作っていないので、記録を残すと同じ依頼の再実行が不要に止まる。
98
+ await rm(recordPath, { force: true });
99
+ throw error;
100
+ }
101
+ resources.push({ type: 'file', file_id: fileId, mount_path: '/CLAUDE.md' });
102
+ }
103
+ const body = {
104
+ agent,
105
+ environment_id: environment,
106
+ ...(request.vault_id ? { vault_ids: [request.vault_id] } : {}),
107
+ title: request.title ?? request.head,
108
+ metadata: { shinaps_request: requestId, head: request.head, staffing: request.staffing },
109
+ budget: {
110
+ type: 'limit',
111
+ max_list_cost: { amount: request.budget_cents, currency: 'USD' },
112
+ },
113
+ resources,
114
+ initial_events: [
115
+ { type: 'user.message', content: [{ type: 'text', text: initialMessage(request) }] },
116
+ ],
117
+ };
118
+ const sessionId = await readId(ant({
119
+ argv: ['beta:sessions', 'create', ...managedBeta, '--transform', 'id', '-r'],
120
+ apiKey: account.apiKey,
121
+ body,
122
+ }), 'ant beta:sessions create');
123
+ await writeFileAtomic(recordPath, JSON.stringify({ ...record, session_id: sessionId }, null, 2) + '\n');
124
+ log(`Created session ${sessionId} for account ${account.name}.`);
125
+ return sessionId;
126
+ });
127
+ }
128
+ // lead の契約 (managed-agents/skills/dev-managed-cc/SKILL.md の初回メッセージ) の必須入力を、
129
+ // すべて明示する。欠けると lead は質問で止まり、それまでの費用が無駄になる。
130
+ function initialMessage(request) {
131
+ const budget = Number(request.budget_cents) / 100;
132
+ return [
133
+ `リポジトリ: ${request.repository.url}(マウント先 ${request.repository.mount_path})`,
134
+ `base: ${request.base} @ ${request.base_sha}`,
135
+ `head: ${request.head}`,
136
+ `範囲と受入条件:\n${request.task.trimEnd()}`,
137
+ `体制: ${request.staffing}`,
138
+ `許可: ${request.allow.length ? request.allow.map((name) => permissionLabels[name]).join('・') : 'なし(commit・push・draft PRの作成はしない)'}`,
139
+ `予算: ${budget} USD(セッションの上限 ${request.budget_cents} セント)`,
140
+ `必須検証(任意): ${request.verify ?? '指定なし。リポジトリの規約に従う'}`,
141
+ `author(任意): ${request.author ?? '指定なし'}`,
142
+ ...(request.claude_md === null ? [] : ['利用者 CLAUDE.md: /mnt/session/uploads/CLAUDE.md']),
143
+ ].join('\n');
144
+ }
145
+ /**
146
+ * このアカウントの記録にあるセッションか確かめる。別アカウントのキーや、CLI が作っていない
147
+ * セッションを観測・操作しないため。
148
+ */
149
+ async function requireRecordedSession(account, sessionId) {
150
+ const directory = join(account.dir, 'sessions');
151
+ const names = await readdir(directory).catch(() => []);
152
+ for (const name of names.filter((entry) => entry.endsWith('.json'))) {
153
+ const record = await readJson(join(directory, name));
154
+ if (record.session_id === sessionId)
155
+ return;
156
+ }
157
+ throw new Error(`Session ${sessionId} was not created by managed run for account ${account.name}.`);
158
+ }
159
+ /** セッションの状態と、lead の最後のメッセージ (1 行目が `状態: 完了|質問|未完了`) を返す。 */
160
+ export async function sessionStatus(account, sessionId, ant) {
161
+ await requireRecordedSession(account, sessionId);
162
+ const call = (argv) => ant({ argv, apiKey: account.apiKey });
163
+ const session = JSON.parse(await call([
164
+ 'beta:sessions',
165
+ 'retrieve',
166
+ '--session-id',
167
+ sessionId,
168
+ ...managedBeta,
169
+ '--format',
170
+ 'json',
171
+ '--transform',
172
+ '{status,usage,budget}',
173
+ ]));
174
+ // 最新の agent.message 1 件だけを取る。全イベント (ツールの呼び出しを含む) を取得しないため。
175
+ const [last] = (await call([
176
+ 'beta:sessions:events',
177
+ 'list',
178
+ '--session-id',
179
+ sessionId,
180
+ ...managedBeta,
181
+ '--type',
182
+ 'agent.message',
183
+ '--order',
184
+ 'desc',
185
+ '--max-items',
186
+ '1',
187
+ '--format',
188
+ 'jsonl',
189
+ '--transform',
190
+ '{content}',
191
+ ]))
192
+ .split('\n')
193
+ .filter((line) => line.trim())
194
+ .map((line) => JSON.parse(line));
195
+ const message = last?.content
196
+ ?.filter((block) => block.type === 'text')
197
+ .map((block) => block.text)
198
+ .join('\n') ?? null;
199
+ const state = message?.match(/^状態: (完了|質問|未完了)/)?.[1] ?? null;
200
+ return { session_id: sessionId, ...session, state, message };
201
+ }
202
+ /** 質問への回答を user.message として送る。新しい許可・予算はここでは加えられない (本文だけ)。 */
203
+ export async function replySession(account, sessionId, text, ant) {
204
+ await requireRecordedSession(account, sessionId);
205
+ await ant({
206
+ argv: ['beta:sessions:events', 'send', '--session-id', sessionId, ...managedBeta],
207
+ apiKey: account.apiKey,
208
+ body: { events: [{ type: 'user.message', content: [{ type: 'text', text }] }] },
209
+ });
210
+ }
211
+ /**
212
+ * セッションの出力ファイルを `sessions/<session_id>/outputs/` へ保存し、保存したパスを返す。
213
+ * 差分の適用はしない。同名のファイルは最新のものだけを保存する (lead は毎ターン changes.patch を
214
+ * 全体の差分で置き換えるため)。
215
+ */
216
+ export async function collectOutputs(account, sessionId, ant) {
217
+ await requireRecordedSession(account, sessionId);
218
+ const listed = (await ant({
219
+ argv: [
220
+ 'beta:files',
221
+ 'list',
222
+ '--scope-id',
223
+ sessionId,
224
+ ...filesBeta,
225
+ '--format',
226
+ 'jsonl',
227
+ '--transform',
228
+ '{id,filename,size_bytes,created_at}',
229
+ ],
230
+ apiKey: account.apiKey,
231
+ }))
232
+ .split('\n')
233
+ .filter((line) => line.trim())
234
+ .map((line) => JSON.parse(line));
235
+ const latest = new Map();
236
+ for (const file of listed) {
237
+ // 名前はクラウドのエージェントが決める。保存先の外へ書かないよう、ファイル名だけのものに限る。
238
+ // ID は ant の引数に渡すので、フラグとして解釈される形 (`-` で始まる) を受け付けない。
239
+ if (!/^[^/\\\0]+$/.test(file.filename) ||
240
+ file.filename === '.' ||
241
+ file.filename === '..' ||
242
+ !/^[A-Za-z0-9_]+$/.test(file.id)) {
243
+ throw new Error(`Refusing to save an output with an unsafe file name (file ${file.id}).`);
244
+ }
245
+ const current = latest.get(file.filename);
246
+ // 時刻は小数秒の桁数が揃うとは限らないので、文字列ではなく時刻として比べる。
247
+ if (!current || Date.parse(current.created_at) < Date.parse(file.created_at)) {
248
+ latest.set(file.filename, file);
249
+ }
250
+ }
251
+ const directory = join(account.dir, 'sessions', sessionId, 'outputs');
252
+ await mkdir(directory, { recursive: true });
253
+ const saved = [];
254
+ for (const file of latest.values()) {
255
+ const path = join(directory, file.filename);
256
+ const temporary = join(directory, `.${file.filename}.${randomUUID()}.tmp`);
257
+ try {
258
+ await ant({
259
+ argv: ['beta:files', 'download', '--file-id', file.id, ...filesBeta, '--output', temporary],
260
+ apiKey: account.apiKey,
261
+ });
262
+ const { size } = await stat(temporary);
263
+ if (file.size_bytes !== undefined && size !== file.size_bytes) {
264
+ throw new Error(`Downloaded ${file.filename} has ${size} bytes, but the Files API lists ${file.size_bytes}.`);
265
+ }
266
+ await rename(temporary, path);
267
+ }
268
+ finally {
269
+ await rm(temporary, { force: true });
270
+ }
271
+ saved.push(path);
272
+ }
273
+ return saved;
274
+ }