-
Notifications
You must be signed in to change notification settings - Fork 6
fix: 非対話更新で kit の hook エントリが重複する問題を修正(v0.77.0) #165
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -167,41 +167,84 @@ _merge_settings_mdm_documents() { | |
| # - Items in snapshot but not newkit, still in current | ||
| # → kit removed → ask (interactive) | ||
| # keep (non-interactive) | ||
| # - Deduplicate via jq 'unique' | ||
| # - Items whose IDENTITY the kit shipped before and still ships | ||
| # → kit modified → kit's version wins | ||
| # - Deduplicate while preserving order | ||
| # | ||
| # Identity exists because elements are compared by exact equality. A kit-side | ||
| # EDIT of a hook entry (matcher, async, asyncTimeout) makes the live copy | ||
| # unequal to every snapshot element, so it used to be classified as a user | ||
| # addition and re-appended NEXT TO the kit's new version — leaving the same | ||
| # hook registered twice, permanently (the snapshot stores the kit build, so | ||
| # the stale copy is re-classified as a user addition on every later update). | ||
| # Hook entries carry a "hooks" array of commands; that command set is the | ||
| # logical registration, independent of the matcher it is bound to. A renamed | ||
| # command changes the identity itself and is out of scope here — retired and | ||
| # renamed commands are swept by _strip_retired_hook_entries instead. Any | ||
| # other element (permissions strings, plain scalars) is its own identity, so | ||
| # an identity match is impossible for them and their behaviour is unchanged. | ||
| # --------------------------------------------------------------------------- | ||
| _merge_arrays_3way() { | ||
| local s_val="$1" # snapshot array JSON | ||
| local c_val="$2" # current array JSON | ||
| local n_val="$3" # new-kit array JSON | ||
|
|
||
| # Determine items to keep/remove through set operations via jq | ||
| # user_added = current \ snapshot (user introduced these) | ||
| # user_added = current \ snapshot, minus superseded kit generations | ||
| # kit_removed = snapshot \ newkit, still in current (kit deleted these) | ||
| local user_added kit_removed | ||
| user_added="$(jq -n \ | ||
| --argjson s "$s_val" \ | ||
| --argjson c "$c_val" \ | ||
| '[$c[] | select(. as $item | $s | map(. == $item) | any | not)]')" || return 1 | ||
| --argjson n "$n_val" \ | ||
| 'def ident: if type == "object" and ((.hooks? | type) == "array") | ||
| then [.hooks[]?.command?] else . end; | ||
| ($s | map(ident)) as $sids | | ||
| ($n | map(ident)) as $nids | | ||
| [ $c[] | ||
| | . as $item | ||
| | ($item | ident) as $id | ||
| | select(($s | any(. == $item)) | not) | ||
| # Superseded kit generation, not a user addition: the kit shipped this | ||
| # identity before and still ships it, so the live copy is an older or | ||
| # locally edited generation of an entry the kit owns. Drop it and let | ||
| # the kit version in $n stand alone. | ||
| | select((($sids | any(. == $id)) and ($nids | any(. == $id))) | not) | ||
| ]')" || return 1 | ||
|
|
||
| kit_removed="$(jq -n \ | ||
| --argjson s "$s_val" \ | ||
| --argjson c "$c_val" \ | ||
| --argjson n "$n_val" \ | ||
| '[ | ||
| 'def ident: if type == "object" and ((.hooks? | type) == "array") | ||
| then [.hooks[]?.command?] else . end; | ||
| ($n | map(ident)) as $nids | | ||
| [ | ||
| $s[] | . as $item | | ||
| select( | ||
| ($n | map(. == $item) | any | not) and | ||
| ($c | map(. == $item) | any) | ||
| ($c | map(. == $item) | any) and | ||
| # Identity still shipped by the new kit → the kit MODIFIED this entry | ||
| # rather than removing it. The updated version in $n replaces this | ||
| # generation; re-adding the snapshot copy would register the same | ||
| # hook twice — the exact failure this merge exists to prevent. | ||
| (($nids | any(. == ($item | ident))) | not) | ||
| ) | ||
| ]')" || return 1 | ||
|
|
||
| # Base result: newkit array (authoritative — already includes kit_added items) | ||
| # plus user additions (items the user added that the kit never had) | ||
| # | ||
| # Dedup preserves order. `unique` sorts, and .hooks.* arrays are ordered: | ||
| # PreToolUse runs in array order and safety-net is required to be first | ||
| # (lib/features.sh, asserted for the build in lib/deploy.sh). Sorting let a | ||
| # user hook whose command sorts earlier take index 0 away from safety-net. | ||
| local merged | ||
| merged="$(jq -n \ | ||
| --argjson n "$n_val" \ | ||
| --argjson ua "$user_added" \ | ||
| '$n + $ua | unique')" || return 1 | ||
| 'reduce ($n + $ua)[] as $x ([]; | ||
| if any(.[]; . == $x) then . else . + [$x] end)')" || return 1 | ||
|
|
||
| # Handle kit-removed items that still exist in current | ||
| local removed_count | ||
|
|
@@ -213,7 +256,8 @@ _merge_arrays_3way() { | |
| merged="$(jq -n \ | ||
| --argjson m "$merged" \ | ||
| --argjson kr "$kit_removed" \ | ||
| '$m + $kr | unique')" || return 1 | ||
| 'reduce ($m + $kr)[] as $x ([]; | ||
| if any(.[]; . == $x) then . else . + [$x] end)')" || return 1 | ||
|
Comment on lines
+259
to
+260
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a non-interactive update first changes a kit hook, the live entry normally still equals the snapshot, so it is excluded from AGENTS.md reference: AGENTS.md:L168-L172 Useful? React with 👍 / 👎. |
||
| else | ||
| # Interactive: ask for each removed item | ||
| local i=0 | ||
|
|
@@ -238,7 +282,8 @@ _merge_arrays_3way() { | |
| merged="$(jq -n \ | ||
| --argjson m "$merged" \ | ||
| --argjson item "$item" \ | ||
| '$m + [$item] | unique')" || return 1 | ||
| 'reduce ($m + [$item])[] as $x ([]; | ||
| if any(.[]; . == $x) then . else . + [$x] end)')" || return 1 | ||
| ;; | ||
| esac | ||
| i=$((i + 1)) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a user appends a custom command to an existing kit-owned outer hook entry, this identity becomes
[kit-command, user-command]instead of the snapshot/new-kit identity[kit-command]. On a non-interactive update that changes the kit entry's matcher or async fields, the reducer therefore retains the entire edited entry alongside the new kit entry, causing the kit command to execute twice; the post-merge sweep cannot heal it for the same identity mismatch. This is a valid way to customize the innerhooksarray and conflicts with the update contract to preserve user-added settings, so identity matching needs to recognize the kit command independently of additional user commands.AGENTS.md reference: AGENTS.md:L168-L172
Useful? React with 👍 / 👎.