feat: 下流リポジトリ同期の manifest と同期エンジンを追加 - #917
Conversation
.claude/hooks/README.md と AGENTS.md が prettier --check に失敗し pre-commit ゲートを塞いでいたため整形。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Jest がカバレッジ閾値未達を 'coverage threshold for <kind> (<n>%) not met' 形式で出力するようになり、旧文言 'does not meet "global" threshold' を 期待するアサーションが失敗していた。実際の出力形式にマッチする正規表現に変更。 Closes #914 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
テンプレートと .claude/ アセットの下流追従を自動化する基盤: - .github/sync-downstream.json: groups + repos オプトイン形式の同期 manifest (per-repo exclude 対応、5下流リポジトリの実在ワークフロー構成を反映) - script/sync-downstream.js: git/gh 非依存の純粋ファイル同期エンジン (__pycache__/*.pyc 除外、--check dry-run、module.exports で Jest 直接テスト) - test/sync-downstream.test.js: スキーマ検証・実 manifest 整合・コピー挙動の27テスト - docs/adr/0017-downstream-template-auto-sync.md: ADR 0009 を amend PR 作成を行う GitHub Actions ワークフローは後続 PR で追加する。 Closes #916 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
✅ Files skipped from review due to trivial changes (2)
📝 WalkthroughWalkthroughIntroduces a manifest-driven downstream template sync system: a JSON manifest defining sync groups/repos, a Node.js CLI sync engine ( ChangesDownstream Template Auto-Sync
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant CLI
participant SyncScript as sync-downstream.js
participant Manifest as sync-downstream.json
participant FS as Filesystem
CLI->>SyncScript: parseArgs(--repo, --target, --check)
SyncScript->>Manifest: loadManifest()
SyncScript->>SyncScript: resolveFilesForRepo(manifest, repo)
SyncScript->>FS: listSourceFiles(entries)
SyncScript->>FS: syncFiles(entries, exclude, check)
FS-->>SyncScript: copied/unchanged/excluded
SyncScript-->>CLI: summary output
Possibly related issues
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 938cd3dbc3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| for (const entry of resolved.entries) { | ||
| for (const file of listSourceFiles(configRoot, entry)) { |
There was a problem hiding this comment.
Remove stale files from managed directories
When a managed directory loses a file upstream, this loop only enumerates files that still exist in the source and never inspects the target directory, so downstream checkouts keep removed or renamed hooks/rules after the sync PR. For directory groups such as .claude/hooks/ and .claude/rules/, that means obsolete rules can continue to run in downstream repos instead of converging to config; please delete target files under managed directories that are no longer present unless they are excluded.
Useful? React with 👍 / 👎.
|
(aside) |
|
Claude finished @keito4's task in 4m 52s —— View job コンフリクト解消作業 ✅
作業サマリー
コンフリクト箇所:
変更後の差分(vs main): 本PRの意図する5ファイルのみ
|
origin/main (767c4bb) の変更を取り込み、コンフリクトを解消。 コンフリクト箇所: - docs/adr/README.md: ADR 0016 タイトル更新と ADR 0017 追加を統合 Co-authored-by: keito4 <keito4@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (6)
test/sync-downstream.test.js (1)
116-135: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a test for malformed
excludeschema.Given the
validateRepogap flagged inscript/sync-downstream.js, consider adding a test that a non-arrayexcludefield is rejected once that validation is added, to prevent regression.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/sync-downstream.test.js` around lines 116 - 135, Add a regression test in resolveFilesForRepo coverage for malformed repo exclude data: extend the existing test block around validManifest(), resolveFilesForRepo, and validateRepo expectations so a repo with a non-array exclude value is rejected. Update the sync-downstream validation path to enforce the repo schema before resolveFilesForRepo consumes it, and assert that the invalid exclude shape throws rather than being treated as a set.script/sync-downstream.js (1)
195-213: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win
main()doesn't handleloadManifest/resolveFilesForRepoerrors gracefully.If the manifest fails validation or
--manifestpoints to a bad path,loadManifest/resolveFilesForRepothrow and the process exits with a raw Node stack trace instead of the cleanusage:messaging already used for missing args.♻️ Proposed fix
function main() { const args = parseArgs(process.argv.slice(2)); if (args.repo === undefined || args.target === undefined) { console.error('usage: sync-downstream.js --repo <owner/name> --target <dir> [--check] [--manifest <path>]'); process.exit(2); } - const manifest = loadManifest(args.manifest ?? DEFAULT_MANIFEST); - const resolved = resolveFilesForRepo(manifest, args.repo); - const result = syncFiles(repoRoot, path.resolve(args.target), resolved, { check: args.check }); + let result; + try { + const manifest = loadManifest(args.manifest ?? DEFAULT_MANIFEST); + const resolved = resolveFilesForRepo(manifest, args.repo); + result = syncFiles(repoRoot, path.resolve(args.target), resolved, { check: args.check }); + } catch (error) { + console.error(`sync-downstream: ${error.message}`); + process.exit(1); + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@script/sync-downstream.js` around lines 195 - 213, main() currently lets loadManifest and resolveFilesForRepo throw uncaught errors, which produces a raw stack trace instead of a clean CLI failure. Wrap the manifest loading and repo resolution path in main() with error handling, using the existing parseArgs, loadManifest, and resolveFilesForRepo flow to catch bad --manifest paths or validation errors. On failure, print a concise user-facing message to stderr that matches the CLI style already used for missing args, then exit with a nonzero status.test/required-workflow-trigger.test.js (1)
7-31: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider
os.tmpdir()instead of a repo-local.contextscratch dir.Creating/removing temp dirs under
path.join(repoPath, '.context')works, but if the process crashes before thefinallyblock runs, leftover directories pollute the repo working tree. Usingfs.mkdtempSync(path.join(os.tmpdir(), 'required-workflow-test-'))avoids that risk entirely.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/required-workflow-trigger.test.js` around lines 7 - 31, The temporary workspace in runRequiredWorkflowScript is being created under a repo-local .context directory, which can leave junk in the working tree if cleanup is skipped. Switch the scratch directory creation to use a system temp location via os.tmpdir() in runRequiredWorkflowScript, while keeping the rest of the workflow file setup and cleanup logic the same.docs/mcp-servers-guide.md (1)
59-64: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winInconsistent error message between duplicated GitHub MCP snippets.
The standalone GitHub config (Line 63) includes actionable guidance in the missing-token error message, but the "complete configuration example" version (Line 145) drops that guidance. Align both snippets to avoid confusing readers who copy the shorter one.
✏️ Suggested fix
- "TOKEN=\"${GITHUB_PERSONAL_ACCESS_TOKEN:-${GITHUB_TOKEN:-$(gh auth token 2>/dev/null)}}\"; [ -n \"$TOKEN\" ] || { echo 'GitHub MCP: missing token' >&2; exit 1; }; export GITHUB_PERSONAL_ACCESS_TOKEN=\"$TOKEN\"; exec npx --yes `@modelcontextprotocol/server-github`" + "TOKEN=\"${GITHUB_PERSONAL_ACCESS_TOKEN:-${GITHUB_TOKEN:-$(gh auth token 2>/dev/null)}}\"; [ -n \"$TOKEN\" ] || { echo 'GitHub MCP: missing token (run gh auth login or set GITHUB_PERSONAL_ACCESS_TOKEN/GITHUB_TOKEN)' >&2; exit 1; }; export GITHUB_PERSONAL_ACCESS_TOKEN=\"$TOKEN\"; exec npx --yes `@modelcontextprotocol/server-github`"Also applies to: 137-146
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/mcp-servers-guide.md` around lines 59 - 64, The GitHub MCP server stdio command has inconsistent missing-token messaging between the standalone config and the complete configuration example. Update the command string used in the GitHub MCP snippet so both versions include the same actionable guidance about running gh auth login or setting GITHUB_PERSONAL_ACCESS_TOKEN/GITHUB_TOKEN, keeping the text aligned across the duplicated examples.script/macos/select-input-source.swift (1)
1-78: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoffInterpreted Swift invocation adds hotkey latency.
This script is executed via
xcrun swift "$src"(seeagent-select-input-source.sh) on every skhd hotkey press. Interpreting a.swiftfile from source on each call incurs noticeable startup overhead compared to a precompiled binary, which will be perceptible for a global keyboard shortcut expected to respond instantly.Consider having the Nix module compile this file (e.g., via
swiftc) into a binary during build/activation, and have the wrapper exec the compiled binary directly instead ofxcrun swift.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@script/macos/select-input-source.swift` around lines 1 - 78, The hotkey path is running the Swift source through the interpreter instead of a compiled executable, which adds avoidable startup latency. Update the build/activation flow to compile the select-input-source.swift script with swiftc into a binary, then change agent-select-input-source.sh to exec that binary directly instead of invoking xcrun swift. Use the select-input-source main script entrypoint and the wrapper’s exec path as the places to update.nix/hosts/darwin/default.nix (1)
134-141: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoffHardcoded absolute path with personal username in
skhdConfig.
/Users/keito/.local/bin/select-input-sourcebakes in a specific username. Since this repo is being synced as a template to downstream repos (per this PR's stated objective), a hardcoded path here won't resolve correctly for other users/machines.Consider interpolating the home directory dynamically if the module has access to it at this evaluation point (e.g., via
config.home.homeDirectoryfrom home-manager integration or a similar nix-darwin binding).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@nix/hosts/darwin/default.nix` around lines 134 - 141, The skhdConfig in the services.skhd block hardcodes a user-specific absolute path, which will break downstream templates for other usernames. Update the select-input-source command path to be derived dynamically from the current user’s home directory, using an available Nix binding such as config.home.homeDirectory or an equivalent nix-darwin/home-manager value, so the command resolves correctly on any machine.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@script/sync-downstream.js`:
- Around line 53-65: `validateRepo` currently validates `name` and `groups` but
misses the optional `exclude` field, which later gets consumed by
`resolveFilesForRepo`. Add schema validation in `validateRepo` to ensure
`repo.exclude`, when present, is an array of strings before it reaches `new
Set(repo.exclude ?? [])`, and throw a manifest error if it is not. Use
`validateRepo` and `resolveFilesForRepo` as the key places to align the
validation with the runtime expectation.
---
Nitpick comments:
In `@docs/mcp-servers-guide.md`:
- Around line 59-64: The GitHub MCP server stdio command has inconsistent
missing-token messaging between the standalone config and the complete
configuration example. Update the command string used in the GitHub MCP snippet
so both versions include the same actionable guidance about running gh auth
login or setting GITHUB_PERSONAL_ACCESS_TOKEN/GITHUB_TOKEN, keeping the text
aligned across the duplicated examples.
In `@nix/hosts/darwin/default.nix`:
- Around line 134-141: The skhdConfig in the services.skhd block hardcodes a
user-specific absolute path, which will break downstream templates for other
usernames. Update the select-input-source command path to be derived dynamically
from the current user’s home directory, using an available Nix binding such as
config.home.homeDirectory or an equivalent nix-darwin/home-manager value, so the
command resolves correctly on any machine.
In `@script/macos/select-input-source.swift`:
- Around line 1-78: The hotkey path is running the Swift source through the
interpreter instead of a compiled executable, which adds avoidable startup
latency. Update the build/activation flow to compile the
select-input-source.swift script with swiftc into a binary, then change
agent-select-input-source.sh to exec that binary directly instead of invoking
xcrun swift. Use the select-input-source main script entrypoint and the
wrapper’s exec path as the places to update.
In `@script/sync-downstream.js`:
- Around line 195-213: main() currently lets loadManifest and
resolveFilesForRepo throw uncaught errors, which produces a raw stack trace
instead of a clean CLI failure. Wrap the manifest loading and repo resolution
path in main() with error handling, using the existing parseArgs, loadManifest,
and resolveFilesForRepo flow to catch bad --manifest paths or validation errors.
On failure, print a concise user-facing message to stderr that matches the CLI
style already used for missing args, then exit with a nonzero status.
In `@test/required-workflow-trigger.test.js`:
- Around line 7-31: The temporary workspace in runRequiredWorkflowScript is
being created under a repo-local .context directory, which can leave junk in the
working tree if cleanup is skipped. Switch the scratch directory creation to use
a system temp location via os.tmpdir() in runRequiredWorkflowScript, while
keeping the rest of the workflow file setup and cleanup logic the same.
In `@test/sync-downstream.test.js`:
- Around line 116-135: Add a regression test in resolveFilesForRepo coverage for
malformed repo exclude data: extend the existing test block around
validManifest(), resolveFilesForRepo, and validateRepo expectations so a repo
with a non-array exclude value is rejected. Update the sync-downstream
validation path to enforce the repo schema before resolveFilesForRepo consumes
it, and assert that the invalid exclude shape throws rather than being treated
as a set.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: bbf984e3-87ef-45a2-98f1-7b8d0c1671c5
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (29)
.claude/hooks/stop_test_verification.py.devcontainer/Dockerfile.devcontainer/claude-settings.json.filelengthignore.github/sync-downstream.json.gitignoredocs/adr/0016-use-kanary-for-keyboard-remapping.mddocs/adr/0017-downstream-template-auto-sync.mddocs/adr/README.mddocs/mcp-servers-guide.mddocs/tool-catalog.mdnix/home/default.nixnix/home/input-source.nixnix/hosts/darwin/default.nixpackage.jsonscript/macos/agent-select-input-source.shscript/macos/select-input-source.swiftscript/sync-downstream.jstest/brew-categories.test.jstest/claude-workflow-contract.test.jstest/hooks-integrity.test.jstest/hooks-lifecycle.test.jstest/hooks-post-pr-tools.test.jstest/hooks-post-tools.test.jstest/integration/lib_functions.batstest/nix-darwin-config.test.jstest/required-workflow-trigger.test.jstest/settings-hooks.test.jstest/sync-downstream.test.js
💤 Files with no reviewable changes (1)
- test/claude-workflow-contract.test.js
| function validateRepo(repo, groups) { | ||
| if (typeof repo.name !== 'string' || !/^[\w.-]+\/[\w.-]+$/u.test(repo.name)) { | ||
| throw new Error(`manifest: invalid repo name: ${JSON.stringify(repo.name)}`); | ||
| } | ||
| if (!Array.isArray(repo.groups) || repo.groups.length === 0) { | ||
| throw new Error(`manifest: repo ${repo.name} must opt into at least one group`); | ||
| } | ||
| for (const group of repo.groups) { | ||
| if (!Object.hasOwn(groups, group)) { | ||
| throw new Error(`manifest: repo ${repo.name} references unknown group "${group}"`); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Missing schema validation for repo.exclude.
validateRepo checks name and groups, but never validates the optional exclude field. In resolveFilesForRepo (Line 110), new Set(repo.exclude ?? []) is called directly on whatever value is present — if a manifest author accidentally sets exclude to a string instead of an array (e.g. "exclude": ".claude/hooks/common.py"), new Set(...) will silently split it into individual characters, and the exclusion will silently fail to match any real path. Since manifest correctness is the explicit purpose of validateManifest/validateRepo, this gap defeats that safety net for a field the engine actively consumes.
🛠️ Proposed fix
function validateRepo(repo, groups) {
if (typeof repo.name !== 'string' || !/^[\w.-]+\/[\w.-]+$/u.test(repo.name)) {
throw new Error(`manifest: invalid repo name: ${JSON.stringify(repo.name)}`);
}
if (!Array.isArray(repo.groups) || repo.groups.length === 0) {
throw new Error(`manifest: repo ${repo.name} must opt into at least one group`);
}
for (const group of repo.groups) {
if (!Object.hasOwn(groups, group)) {
throw new Error(`manifest: repo ${repo.name} references unknown group "${group}"`);
}
}
+ if (repo.exclude !== undefined && (!Array.isArray(repo.exclude) || repo.exclude.some((p) => typeof p !== 'string'))) {
+ throw new Error(`manifest: repo ${repo.name} "exclude" must be an array of strings`);
+ }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function validateRepo(repo, groups) { | |
| if (typeof repo.name !== 'string' || !/^[\w.-]+\/[\w.-]+$/u.test(repo.name)) { | |
| throw new Error(`manifest: invalid repo name: ${JSON.stringify(repo.name)}`); | |
| } | |
| if (!Array.isArray(repo.groups) || repo.groups.length === 0) { | |
| throw new Error(`manifest: repo ${repo.name} must opt into at least one group`); | |
| } | |
| for (const group of repo.groups) { | |
| if (!Object.hasOwn(groups, group)) { | |
| throw new Error(`manifest: repo ${repo.name} references unknown group "${group}"`); | |
| } | |
| } | |
| } | |
| function validateRepo(repo, groups) { | |
| if (typeof repo.name !== 'string' || !/^[\w.-]+\/[\w.-]+$/u.test(repo.name)) { | |
| throw new Error(`manifest: invalid repo name: ${JSON.stringify(repo.name)}`); | |
| } | |
| if (!Array.isArray(repo.groups) || repo.groups.length === 0) { | |
| throw new Error(`manifest: repo ${repo.name} must opt into at least one group`); | |
| } | |
| for (const group of repo.groups) { | |
| if (!Object.hasOwn(groups, group)) { | |
| throw new Error(`manifest: repo ${repo.name} references unknown group "${group}"`); | |
| } | |
| } | |
| if (repo.exclude !== undefined && (!Array.isArray(repo.exclude) || repo.exclude.some((p) => typeof p !== 'string'))) { | |
| throw new Error(`manifest: repo ${repo.name} "exclude" must be an array of strings`); | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@script/sync-downstream.js` around lines 53 - 65, `validateRepo` currently
validates `name` and `groups` but misses the optional `exclude` field, which
later gets consumed by `resolveFilesForRepo`. Add schema validation in
`validateRepo` to ensure `repo.exclude`, when present, is an array of strings
before it reaches `new Set(repo.exclude ?? [])`, and throw a manifest error if
it is not. Use `validateRepo` and `resolveFilesForRepo` as the key places to
align the validation with the runtime expectation.
|
(aside) |
|
Claude finished @keito4's task in 9m 4s —— View job コンフリクト解消完了 ✅
作業サマリー
取り込まれた main の変更:
コンフリクト解消方針:
|
origin/main (7e773f0 - PR #944 Prefer Raycast and AeroSpace macOS controls) の変更を取り込み、コンフリクトを解消。 コンフリクト箇所: - docs/adr/README.md: 両側の変更を統合(ADR 0017 エントリを保持) - nix/hosts/darwin/default.nix: main の agent-deck-web launchd エージェントを追加 - test/nix-darwin-config.test.js: main の Dock・メニューバー・AeroSpace テストを追加 Co-authored-by: keito4 <keito4@users.noreply.github.com>
|
(aside) |
|
Claude finished @keito4's task in 3m 55s —— View job コンフリクト解消完了 ✅
作業サマリー
コンフリクト箇所:
変更後の差分(vs main): 本 PR の意図する5ファイルのみ
|
…stream-sync-foundation - ADR 番号重複を解消: 本 PR の 0017-downstream-template-auto-sync.md を 0018 にリナンバリング - main 側の ADR 0017 (manage-bettertouchtool-gestures) を採用 - docs/adr/README.md に 0017・0018 両エントリを追加 Co-authored-by: keito4 <keito4@users.noreply.github.com>
|
(aside) |
|
Claude finished @keito4's task in 2m 49s —— View job コンフリクト解消完了 ✅
作業サマリー
コンフリクト箇所:
変更後の差分(vs main): 本 PR の意図する5ファイルのみ
|
…nstream-sync-foundation - ADR 番号重複を解消:本 PR の 0018-downstream-template-auto-sync.md → 0019 にリナンバリング - main が追加した 0018-reusable-workflow-distribution.md を採用 - docs/adr/README.md のコンフリクトを解消(両 ADR エントリを統合) Co-authored-by: keito4 <keito4@users.noreply.github.com>
|
🎉 This PR is included in version 1.122.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
…926,#958 etc.) - Resolve templates/README.md conflict: keep both TAKT note and new downstream distribution section from main - Add missing runReleaseTypeRule() function to test/commitlint-config.test.js that was included in main's tests but absent from the merged result - Take managed-file-edit hook, sync-downstream workflow, and related ADR/tests from main (PR#923 feat/922-managed-file-edit-hook, PR#919 sync-downstream) Co-authored-by: keito4 <keito4@users.noreply.github.com>
Why
config のテンプレートと .claude/ アセット(hooks, rules, settings)は setup 時のコピー配布のみで、更新後の下流5リポジトリへの追従は手動の
/repo-maintenance頼み(check_downstream_sync()は警告表示のみ)。config を直しても利用側に伝わらず、保守性のボトルネックになっていた。What
自動同期パイプラインの基盤(1/2):
.github/sync-downstream.json— 同期 manifest。groups(source→target のマッピング集合)+ repos のグループオプトイン + per-repoexclude。5下流リポジトリの実在ワークフロー構成を調査して反映済みscript/sync-downstream.js— 純粋ファイル同期エンジン。git/gh 非依存(checkout/commit/PR はワークフロー側の責務)。__pycache__/*.pyc恒久除外、--checkdry-run、module.exports 公開で Jest 直接テスト可能test/sync-downstream.test.js— 27テスト(スキーマ検証、チェックイン済み manifest と実ファイルツリーの整合、copy/unchanged/exclude/ignore/check の挙動)docs/adr/0017-downstream-template-auto-sync.md— ADR 0009 を amend後続 PR で push 契機の fan-out ワークフロー(peter-evans/create-pull-request による冪等 PR 作成)を追加する。
How
node script/sync-downstream.js --repo keito4/raycast-extensions --target <dir> --checkで実 manifest に対する dry-run を確認済み(claude-config のみの19ファイル、intent-gate-android はワークフロー4種を加えた23ファイル、pycache 除外)。Risk
新規ファイルのみで既存動作への影響なし。実際の下流書き込みは後続 PR のワークフロー導入まで発生しない。
Closes #916
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation