Skip to content

feat: 下流リポジトリ同期の manifest と同期エンジンを追加 - #917

Merged
keito4 merged 9 commits into
mainfrom
feat/916-downstream-sync-foundation
Jul 12, 2026
Merged

keito4 merged 9 commits into
mainfrom
feat/916-downstream-sync-foundation

Conversation

@keito4

@keito4 keito4 commented Jul 3, 2026

Copy link
Copy Markdown
Owner

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-repo exclude。5下流リポジトリの実在ワークフロー構成を調査して反映済み
  • script/sync-downstream.js — 純粋ファイル同期エンジン。git/gh 非依存(checkout/commit/PR はワークフロー側の責務)。__pycache__/*.pyc 恒久除外、--check dry-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

    • Added automated syncing for shared templates and assets to selected downstream repositories.
    • Introduced a dry-run option to preview sync changes before applying them.
  • Documentation

    • Added a new ADR describing the downstream auto-sync approach.
    • Updated the ADR index with the new entry.

keito4 and others added 3 commits July 3, 2026 14:34
.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>
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 523a02a8-5756-452e-b5d5-d2a80d8fa107

📥 Commits

Reviewing files that changed from the base of the PR and between 5d3ea3f and f01eede.

📒 Files selected for processing (2)
  • docs/adr/0019-downstream-template-auto-sync.md
  • docs/adr/README.md
✅ Files skipped from review due to trivial changes (2)
  • docs/adr/0019-downstream-template-auto-sync.md
  • docs/adr/README.md

📝 Walkthrough

Walkthrough

Introduces a manifest-driven downstream template sync system: a JSON manifest defining sync groups/repos, a Node.js CLI sync engine (script/sync-downstream.js) that copies or diffs files without git/gh dependencies, accompanying Jest tests, and ADR 0019 documenting the design plus an ADR index entry.

Changes

Downstream Template Auto-Sync

Layer / File(s) Summary
Sync manifest and ADR
.github/sync-downstream.json, docs/adr/0019-downstream-template-auto-sync.md, docs/adr/README.md
Adds the manifest defining sync groups and per-repo opt-ins/exclusions, documents the design as ADR 0019, and lists it in the ADR index as Accepted.
Sync engine implementation
script/sync-downstream.js
Implements isIgnored, manifest validation (validateGroup/validateRepo/validateManifest), loadManifest, resolveFilesForRepo, listSourceFiles, syncFiles (copy/check modes), parseArgs, main, and CommonJS exports.
Sync engine tests
test/sync-downstream.test.js
Adds Jest coverage for manifest validation, checked-in manifest integrity, file resolution, ignore matching, sync/copy/exclude/check behaviors, and CLI argument parsing.

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
Loading

Possibly related issues

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly names the main change: adding a downstream sync manifest and sync engine.
Description check ✅ Passed It covers the motivation, main changes, dry-run verification, and issue link, though the template's Summary/Checklist sections are not fully filled.
Linked Issues check ✅ Passed The PR delivers the requested manifest, pure file-sync script, Jest tests, and ADR for the downstream sync foundation.
Out of Scope Changes check ✅ Passed No obvious out-of-scope code changes appear beyond the requested sync foundation and related documentation updates.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/916-downstream-sync-foundation

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@claude

claude Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Claude finished @keito4's task in 1s —— View job


I'll analyze this and get back to you.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread script/sync-downstream.js
Comment on lines +149 to +150
for (const entry of resolved.entries) {
for (const file of listSourceFiles(configRoot, entry)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@keito4

keito4 commented Jul 6, 2026

Copy link
Copy Markdown
Owner Author

(aside)
@claude PRがbaseにマージする際にコンフリクトが発生しています。最新のbaseからpullし、修正してください。

@claude

claude Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Claude finished @keito4's task in 4m 52s —— View job


コンフリクト解消作業 ✅

  • 現在のブランチ状態を確認(merge-base: 4b486f14
  • origin/main の最新を取得(tip: 767c4bb
  • mainの変更を全ファイルに適用(26ファイル)
  • docs/adr/README.md のコンフリクトを手動解消
    • main側: ADR 0016 タイトルを "Use Kanary and skhd for Keyboard Remapping" に更新
    • branch側: ADR 0017 エントリを追加
    • 解消: 両方の変更を統合
  • コミット・プッシュ完了(86d0689

作業サマリー

origin/main (767c4bb) の変更をブランチに取り込みました。

コンフリクト箇所: docs/adr/README.md のみ

  • mainで更新されたADR 0016のタイトル ("and skhd" が追加) を採用
  • ブランチで追加したADR 0017エントリを保持

変更後の差分(vs main): 本PRの意図する5ファイルのみ

  • .github/sync-downstream.json(新規)
  • docs/adr/0017-downstream-template-auto-sync.md(新規)
  • docs/adr/README.md(ADR 0017行を追加)
  • script/sync-downstream.js(新規)
  • test/sync-downstream.test.js(新規)
    | Branch

origin/main (767c4bb) の変更を取り込み、コンフリクトを解消。

コンフリクト箇所:
- docs/adr/README.md: ADR 0016 タイトル更新と ADR 0017 追加を統合

Co-authored-by: keito4 <keito4@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (6)
test/sync-downstream.test.js (1)

116-135: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a test for malformed exclude schema.

Given the validateRepo gap flagged in script/sync-downstream.js, consider adding a test that a non-array exclude field 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 handle loadManifest/resolveFilesForRepo errors gracefully.

If the manifest fails validation or --manifest points to a bad path, loadManifest/resolveFilesForRepo throw and the process exits with a raw Node stack trace instead of the clean usage: 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 value

Consider os.tmpdir() instead of a repo-local .context scratch dir.

Creating/removing temp dirs under path.join(repoPath, '.context') works, but if the process crashes before the finally block runs, leftover directories pollute the repo working tree. Using fs.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 win

Inconsistent 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 tradeoff

Interpreted Swift invocation adds hotkey latency.

This script is executed via xcrun swift "$src" (see agent-select-input-source.sh) on every skhd hotkey press. Interpreting a .swift file 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 of xcrun 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 tradeoff

Hardcoded absolute path with personal username in skhdConfig.

/Users/keito/.local/bin/select-input-source bakes 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.homeDirectory from 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

📥 Commits

Reviewing files that changed from the base of the PR and between 4b486f1 and 86d0689.

⛔ Files ignored due to path filters (1)
  • package-lock.json is 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
  • .gitignore
  • docs/adr/0016-use-kanary-for-keyboard-remapping.md
  • docs/adr/0017-downstream-template-auto-sync.md
  • docs/adr/README.md
  • docs/mcp-servers-guide.md
  • docs/tool-catalog.md
  • nix/home/default.nix
  • nix/home/input-source.nix
  • nix/hosts/darwin/default.nix
  • package.json
  • script/macos/agent-select-input-source.sh
  • script/macos/select-input-source.swift
  • script/sync-downstream.js
  • test/brew-categories.test.js
  • test/claude-workflow-contract.test.js
  • test/hooks-integrity.test.js
  • test/hooks-lifecycle.test.js
  • test/hooks-post-pr-tools.test.js
  • test/hooks-post-tools.test.js
  • test/integration/lib_functions.bats
  • test/nix-darwin-config.test.js
  • test/required-workflow-trigger.test.js
  • test/settings-hooks.test.js
  • test/sync-downstream.test.js
💤 Files with no reviewable changes (1)
  • test/claude-workflow-contract.test.js

Comment thread script/sync-downstream.js
Comment on lines +53 to +65
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}"`);
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Suggested change
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.

@keito4

keito4 commented Jul 6, 2026

Copy link
Copy Markdown
Owner Author

(aside)
@claude PRがbaseにマージする際にコンフリクトが発生しています。最新のbaseからpullし、修正してください。

@claude

claude Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Claude finished @keito4's task in 9m 4s —— View job


コンフリクト解消完了 ✅

  • 現在のブランチ状態を確認(shallow clone を --unshallow で完全取得)
  • origin/main を pull(git pull origin main --no-rebase
  • コンフリクトを解消(3ファイル)
    • 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 テストを追加
  • コミット・プッシュ完了(607768e

作業サマリー

origin/main7e773f0 — PR #944 "Prefer Raycast and AeroSpace macOS controls")をブランチに取り込みました。

取り込まれた main の変更:

  • dot/aerospace.tomlnix/home/dotfiles.nixnix/home/zsh.nixnix/modules/homebrew.nix などの macOS 設定更新
  • credentials/templates/ の削除(PR Prefer Raycast and AeroSpace macOS controls #944 で main 側が削除)
  • dot/config/agent-deck/config.tomldot/config/codespaces-secrets/repos.txt の削除

コンフリクト解消方針:

  • docs/adr/README.md — 本 PR で追加した ADR 0017 エントリを保持し、main の変更と統合
  • nix/hosts/darwin/default.nix — main が追加した agent-deck-web headless WebUI launchd エージェントを採用
  • test/nix-darwin-config.test.js — main が追加した Dock・メニューバー・AeroSpace のテストケースを採用
    | Branch

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>
@keito4

keito4 commented Jul 7, 2026

Copy link
Copy Markdown
Owner Author

(aside)
@claude PRがbaseにマージする際にコンフリクトが発生しています。最新のbaseからpullし、修正してください。

@claude

claude Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Claude finished @keito4's task in 3m 55s —— View job


コンフリクト解消完了 ✅

  • 現在のブランチ状態を確認(shallow clone を --unshallow で完全取得)
  • origin/main の最新を確認
  • コンフリクトを解消
    • git pull origin main --no-rebase を実行
    • docs/adr/README.md のコンフリクトを解消
    • ADR 番号重複を解消:本 PR の 0017-downstream-template-auto-sync.md0018-downstream-template-auto-sync.md にリナンバリング
    • ADR ファイル内のタイトルも ADR 0018 に更新
  • コミット・プッシュ完了(5d3ea3f

作業サマリー

origin/mainaab8117 — PR #947 "manage BetterTouchTool gestures")をブランチに取り込みました。

コンフリクト箇所: docs/adr/README.md のみ(ADR 番号重複)

  • main が追加した 0017-manage-bettertouchtool-gestures.md を 0017 として採用
  • 本 PR で追加した 0017-downstream-template-auto-sync.md0018 にリナンバリング(ファイル名・内部タイトルを更新)

変更後の差分(vs main): 本 PR の意図する5ファイルのみ

  • .github/sync-downstream.json(新規)
  • docs/adr/0018-downstream-template-auto-sync.md(新規・リナンバリング済み)
  • docs/adr/README.md(ADR 0018 行を追加)
  • script/sync-downstream.js(新規)
  • test/sync-downstream.test.js(新規)
    | Branch

…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>
@keito4

keito4 commented Jul 9, 2026

Copy link
Copy Markdown
Owner Author

(aside)
@claude PRがbaseにマージする際にコンフリクトが発生しています。最新のbaseからpullし、修正してください。

@claude

claude Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Claude finished @keito4's task in 2m 49s —— View job


コンフリクト解消完了 ✅

  • 現在のブランチ状態を確認
  • origin/main の最新を取得(shallow clone を --unshallow で完全取得)
  • コンフリクトを解消
    • docs/adr/README.md のコンフリクトを解消
    • ADR 番号重複を解消:本 PR の 0018-downstream-template-auto-sync.md0019-downstream-template-auto-sync.md にリナンバリング
    • ADR ファイル内のタイトルも ADR 0019 に更新
  • コミット・プッシュ完了(f01eede

作業サマリー

origin/maine6acfcc — PR #943 "fix/update-dev-tools")をブランチに取り込みました。

コンフリクト箇所: docs/adr/README.md のみ(ADR 番号重複)

  • main が追加した 0018-reusable-workflow-distribution.md を 0018 として採用
  • 本 PR で追加した 0018-downstream-template-auto-sync.md0019 にリナンバリング(ファイル名・内部タイトルを更新)

変更後の差分(vs main): 本 PR の意図する5ファイルのみ

  • .github/sync-downstream.json(新規)
  • docs/adr/0019-downstream-template-auto-sync.md(新規・リナンバリング済み)
  • docs/adr/README.md(ADR 0019 行を追加)
  • script/sync-downstream.js(新規)
  • test/sync-downstream.test.js(新規)
    | Branch

…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>
@keito4
keito4 merged commit 2e993bd into main Jul 12, 2026
2 checks passed
@keito4
keito4 deleted the feat/916-downstream-sync-foundation branch July 12, 2026 11:06
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 1.122.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@github-actions github-actions Bot added the released リリース済み label Jul 12, 2026
github-actions Bot added a commit that referenced this pull request Jul 12, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

released リリース済み

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: 下流リポジトリへのテンプレート自動同期基盤(manifest + 同期スクリプト)

1 participant