create-egregore 0.21.0-runtime-mvp.10 → 0.21.0-runtime-mvp.11
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.
- package/assets/egregore-launcher.js +2 -2
- package/lib/claude-runtime.js +23 -1
- package/package.json +1 -1
- package/runtime/claude/bin/lib/git-sync.sh +11 -1
- package/runtime/claude/bin/lib/greeting.sh +8 -0
- package/runtime/claude/bin/lib/settings-drift.sh +116 -0
- package/runtime/claude/egregore_runtime/issue_cli.py +3 -3
- package/runtime/claude/manifest.json +1 -0
- package/runtime/codex/bin/lib/git-sync.sh +11 -1
- package/runtime/codex/bin/lib/greeting.sh +8 -0
- package/runtime/codex/bin/lib/settings-drift.sh +116 -0
- package/runtime/codex/egregore_runtime/issue_cli.py +3 -3
- package/runtime/codex/manifest.json +1 -0
- package/runtime/pi/bin/lib/git-sync.sh +11 -1
- package/runtime/pi/bin/lib/greeting.sh +8 -0
- package/runtime/pi/bin/lib/settings-drift.sh +116 -0
- package/runtime/pi/egregore_runtime/issue_cli.py +3 -3
- package/runtime/pi/manifest.json +1 -0
- package/runtime/prime/bin/lib/git-sync.sh +11 -1
- package/runtime/prime/bin/lib/greeting.sh +8 -0
- package/runtime/prime/bin/lib/settings-drift.sh +116 -0
- package/runtime/prime/egregore_runtime/issue_cli.py +3 -3
- package/runtime/prime/manifest.json +1 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// egregore-launcher-version: 0.21.0-runtime-mvp.
|
|
2
|
+
// egregore-launcher-version: 0.21.0-runtime-mvp.11
|
|
3
3
|
//
|
|
4
4
|
// egregore — launch and browse Egregore instances from your terminal.
|
|
5
5
|
// Installed by create-egregore. Reads ~/.egregore/instances.json.
|
|
@@ -35,7 +35,7 @@ const crypto = require("node:crypto");
|
|
|
35
35
|
const { spawnSync, spawn } = require("node:child_process");
|
|
36
36
|
const readline = require("node:readline");
|
|
37
37
|
|
|
38
|
-
const VERSION = "0.21.0-runtime-mvp.
|
|
38
|
+
const VERSION = "0.21.0-runtime-mvp.11";
|
|
39
39
|
const REGISTRY = path.join(os.homedir(), ".egregore", "instances.json");
|
|
40
40
|
const DEFAULT_FILE = path.join(os.homedir(), ".egregore", "default");
|
|
41
41
|
const UPDATE_CACHE_FILE = path.join(os.homedir(), ".egregore", "update-check.json");
|
package/lib/claude-runtime.js
CHANGED
|
@@ -76,6 +76,24 @@ function copyFile(src, dest) {
|
|
|
76
76
|
try { fs.chmodSync(dest, fs.statSync(src).mode & 0o777); } catch {}
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
// The generated settings adapter may replace a stale template settings.json —
|
|
80
|
+
// one carrying nothing the adapter lacks — but an org's customized settings
|
|
81
|
+
// (extra hook events or permission allows, e.g. PreCompact/Stop) are
|
|
82
|
+
// authoritative and are never adopted, even when clean and tracked.
|
|
83
|
+
function settingsSafeToAdopt(destFile, srcFile) {
|
|
84
|
+
try {
|
|
85
|
+
const local = JSON.parse(fs.readFileSync(destFile, "utf8"));
|
|
86
|
+
const bundle = JSON.parse(fs.readFileSync(srcFile, "utf8"));
|
|
87
|
+
const bundleHooks = bundle.hooks || {};
|
|
88
|
+
const extraHooks = Object.keys(local.hooks || {}).filter((k) => !(k in bundleHooks));
|
|
89
|
+
const bundleAllow = new Set(bundle.permissions?.allow || []);
|
|
90
|
+
const extraAllow = (local.permissions?.allow || []).filter((entry) => !bundleAllow.has(entry));
|
|
91
|
+
return extraHooks.length === 0 && extraAllow.length === 0;
|
|
92
|
+
} catch {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
79
97
|
// One-time ownership migration for pre-bundle Claude installs. A clean,
|
|
80
98
|
// tracked path is part of the cloned framework template and may be adopted by
|
|
81
99
|
// the package. Dirty/untracked paths are user work and remain untouched.
|
|
@@ -153,7 +171,11 @@ function installClaudeRuntime(egregoreDir) {
|
|
|
153
171
|
summary.skipped += 1; record();
|
|
154
172
|
} else if (previousHash && currentHash === previousHash) {
|
|
155
173
|
copyFile(src, dest); summary.updated += 1; record();
|
|
156
|
-
} else if (
|
|
174
|
+
} else if (
|
|
175
|
+
!previousHash
|
|
176
|
+
&& (rel.replaceAll("\\", "/") !== ".claude/settings.json" || settingsSafeToAdopt(dest, src))
|
|
177
|
+
&& isCleanTrackedFrameworkFile(egregoreDir, rel)
|
|
178
|
+
) {
|
|
157
179
|
copyFile(src, dest); summary.updated += 1; record();
|
|
158
180
|
} else {
|
|
159
181
|
summary.conflicted += 1; summary.conflicts.push(rel);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-egregore",
|
|
3
|
-
"version": "0.21.0-runtime-mvp.
|
|
3
|
+
"version": "0.21.0-runtime-mvp.11",
|
|
4
4
|
"description": "Set up Egregore for your team in one command — installs the egregore launcher and your choice of Claude Code, Codex, or Pi.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -24,6 +24,10 @@ FRAMEWORK_UPDATED_FILES=""
|
|
|
24
24
|
HEALTH_RETRIEVAL="skip"
|
|
25
25
|
SYNC_RECEIPT=""
|
|
26
26
|
|
|
27
|
+
# Settings drift healer + unshared-settings reporting (see the lib header).
|
|
28
|
+
# shellcheck source=bin/lib/settings-drift.sh
|
|
29
|
+
source "$SCRIPT_DIR/bin/lib/settings-drift.sh"
|
|
30
|
+
|
|
27
31
|
# The core repo's integration branch: "develop" unless egregore.json sets
|
|
28
32
|
# base_branch. session-start.sh resolves this before sourcing; resolve it here
|
|
29
33
|
# too so the lib stands alone for other callers.
|
|
@@ -324,8 +328,14 @@ setup_develop() {
|
|
|
324
328
|
return 1
|
|
325
329
|
fi
|
|
326
330
|
else
|
|
327
|
-
# Already on it — fetch couldn't update a checked-out branch, so pull
|
|
331
|
+
# Already on it — fetch couldn't update a checked-out branch, so pull.
|
|
332
|
+
# Launcher/installer settings drift blocks this merge and the refusal
|
|
333
|
+
# is otherwise silent; heal it first, re-apply the launcher's intent
|
|
334
|
+
# after (bin/lib/settings-drift.sh).
|
|
335
|
+
settings_drift_heal
|
|
328
336
|
git merge --ff-only "origin/$BASE_BRANCH" --quiet 2>/dev/null || true
|
|
337
|
+
settings_drift_reapply
|
|
338
|
+
settings_drift_report
|
|
329
339
|
fi
|
|
330
340
|
|
|
331
341
|
# Never report the intended branch as the actual branch. A checkout can fail
|
|
@@ -95,6 +95,14 @@ if [ -n "$SAVED_BRANCH" ]; then
|
|
|
95
95
|
echo " ✓ Auto-saved uncommitted work on $SAVED_BRANCH"
|
|
96
96
|
fi
|
|
97
97
|
|
|
98
|
+
# --- Unshared org-settings drift (launcher writes not yet shared) ---
|
|
99
|
+
if [ "${SETTINGS_ADAPTER_RESTORED:-false}" = "true" ]; then
|
|
100
|
+
echo " ◇ restored org .claude/settings.json — a generated adapter had replaced it locally"
|
|
101
|
+
fi
|
|
102
|
+
if [ -n "${UNSHARED_SETTINGS_KEYS:-}" ]; then
|
|
103
|
+
echo " ◇ org settings changed on this machine (${UNSHARED_SETTINGS_KEYS}) — say \"share settings\" to make them org-wide"
|
|
104
|
+
fi
|
|
105
|
+
|
|
98
106
|
# --- Footer: health + repos + memory (tertiary) ---
|
|
99
107
|
echo "$SEPARATOR"
|
|
100
108
|
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# settings-drift.sh — heal launcher/installer settings drift around the
|
|
3
|
+
# base-branch fast-forward, and surface unshared org-settings changes.
|
|
4
|
+
#
|
|
5
|
+
# Two org-config surfaces accumulate legitimate local modifications outside
|
|
6
|
+
# any session: .claude/settings.json (the installer's generated public-safe
|
|
7
|
+
# adapter can replace the org's richer committed file) and egregore.json
|
|
8
|
+
# (launcher settings verbs: posture, workflow, people removals). Local
|
|
9
|
+
# modifications to files a fast-forward also changes make git refuse the
|
|
10
|
+
# merge — and that refusal used to be silently swallowed, leaving the
|
|
11
|
+
# checkout behind with no explanation.
|
|
12
|
+
#
|
|
13
|
+
# Contract (sourced; caller provides $BASE_BRANCH and runs from the repo
|
|
14
|
+
# root):
|
|
15
|
+
# settings_drift_heal — before the ff-merge. Restores settings.json
|
|
16
|
+
# only when the local copy is a reduced
|
|
17
|
+
# generated adapter (its hook events and
|
|
18
|
+
# permission allows are a subset of the
|
|
19
|
+
# committed file's). Snapshots egregore.json
|
|
20
|
+
# and returns it to HEAD so the merge can run.
|
|
21
|
+
# settings_drift_reapply — after the ff-merge. Re-applies the snapshot's
|
|
22
|
+
# changed settings-owned keys on top of the
|
|
23
|
+
# incoming egregore.json.
|
|
24
|
+
# settings_drift_report — sets UNSHARED_SETTINGS_KEYS to the
|
|
25
|
+
# settings-owned keys where the working file
|
|
26
|
+
# differs from HEAD, for the greeting.
|
|
27
|
+
#
|
|
28
|
+
# Anything dirty beyond these two files is user work and is never touched.
|
|
29
|
+
|
|
30
|
+
_SETTINGS_OWNED_KEYS='["boundary","base_branch","people_removed","features","repos","admins","people"]'
|
|
31
|
+
_SETTINGS_DRIFT_SNAPSHOT=""
|
|
32
|
+
_SETTINGS_DRIFT_BASE=""
|
|
33
|
+
# Read by bin/lib/greeting.sh after git-sync completes.
|
|
34
|
+
export SETTINGS_ADAPTER_RESTORED="false"
|
|
35
|
+
export UNSHARED_SETTINGS_KEYS=""
|
|
36
|
+
|
|
37
|
+
# The generated adapter only ever removes relative to the org file: fewer
|
|
38
|
+
# hook events, fewer permission allows. A local settings.json that carries
|
|
39
|
+
# anything the committed file lacks is user work and stays untouched.
|
|
40
|
+
_settings_is_reduced_adapter() {
|
|
41
|
+
local tmp_head
|
|
42
|
+
tmp_head=$(mktemp) || return 1
|
|
43
|
+
if ! git show "HEAD:.claude/settings.json" > "$tmp_head" 2>/dev/null; then
|
|
44
|
+
rm -f "$tmp_head"
|
|
45
|
+
return 1
|
|
46
|
+
fi
|
|
47
|
+
jq -es '
|
|
48
|
+
.[0] as $local | .[1] as $head |
|
|
49
|
+
($local != $head) and
|
|
50
|
+
((($local.hooks // {}) | keys) - (($head.hooks // {}) | keys) | length == 0) and
|
|
51
|
+
((($local.permissions.allow // []) - ($head.permissions.allow // [])) | length == 0)
|
|
52
|
+
' .claude/settings.json "$tmp_head" >/dev/null 2>&1
|
|
53
|
+
local verdict=$?
|
|
54
|
+
rm -f "$tmp_head"
|
|
55
|
+
return "$verdict"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
settings_drift_heal() {
|
|
59
|
+
git rev-parse -q --verify "origin/$BASE_BRANCH" >/dev/null 2>&1 || return 0
|
|
60
|
+
[ "$(git rev-parse HEAD 2>/dev/null)" = "$(git rev-parse "origin/$BASE_BRANCH" 2>/dev/null)" ] && return 0
|
|
61
|
+
|
|
62
|
+
if ! git diff --quiet -- .claude/settings.json 2>/dev/null; then
|
|
63
|
+
if _settings_is_reduced_adapter; then
|
|
64
|
+
git checkout -- .claude/settings.json 2>/dev/null && SETTINGS_ADAPTER_RESTORED="true"
|
|
65
|
+
fi
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
if ! git diff --quiet -- egregore.json 2>/dev/null; then
|
|
69
|
+
_SETTINGS_DRIFT_SNAPSHOT=$(cat egregore.json 2>/dev/null || true)
|
|
70
|
+
_SETTINGS_DRIFT_BASE=$(git show "HEAD:egregore.json" 2>/dev/null || true)
|
|
71
|
+
if [ -n "$_SETTINGS_DRIFT_SNAPSHOT" ] && [ -n "$_SETTINGS_DRIFT_BASE" ] \
|
|
72
|
+
&& printf '%s' "$_SETTINGS_DRIFT_SNAPSHOT" | jq -e . >/dev/null 2>&1; then
|
|
73
|
+
git checkout -- egregore.json 2>/dev/null || _SETTINGS_DRIFT_SNAPSHOT=""
|
|
74
|
+
else
|
|
75
|
+
_SETTINGS_DRIFT_SNAPSHOT=""
|
|
76
|
+
fi
|
|
77
|
+
fi
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
settings_drift_reapply() {
|
|
81
|
+
[ -n "$_SETTINGS_DRIFT_SNAPSHOT" ] || return 0
|
|
82
|
+
local tmp_local tmp_base merged
|
|
83
|
+
tmp_local=$(mktemp) && tmp_base=$(mktemp) || return 0
|
|
84
|
+
printf '%s' "$_SETTINGS_DRIFT_SNAPSHOT" > "$tmp_local"
|
|
85
|
+
printf '%s' "$_SETTINGS_DRIFT_BASE" > "$tmp_base"
|
|
86
|
+
merged=$(jq -s --argjson keys "$_SETTINGS_OWNED_KEYS" '
|
|
87
|
+
.[0] as $incoming | .[1] as $local | .[2] as $base |
|
|
88
|
+
reduce $keys[] as $k ($incoming;
|
|
89
|
+
if ($local[$k] // null) != ($base[$k] // null) then
|
|
90
|
+
if ($local | has($k)) then .[$k] = $local[$k] else del(.[$k]) end
|
|
91
|
+
else . end)
|
|
92
|
+
' egregore.json "$tmp_local" "$tmp_base" 2>/dev/null || true)
|
|
93
|
+
rm -f "$tmp_local" "$tmp_base"
|
|
94
|
+
if [ -n "$merged" ] && printf '%s' "$merged" | jq -e . >/dev/null 2>&1; then
|
|
95
|
+
printf '%s\n' "$merged" > egregore.json
|
|
96
|
+
fi
|
|
97
|
+
_SETTINGS_DRIFT_SNAPSHOT=""
|
|
98
|
+
_SETTINGS_DRIFT_BASE=""
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
settings_drift_report() {
|
|
102
|
+
UNSHARED_SETTINGS_KEYS=""
|
|
103
|
+
git diff --quiet -- egregore.json 2>/dev/null && return 0
|
|
104
|
+
local tmp_head
|
|
105
|
+
tmp_head=$(mktemp) || return 0
|
|
106
|
+
if ! git show "HEAD:egregore.json" > "$tmp_head" 2>/dev/null; then
|
|
107
|
+
rm -f "$tmp_head"
|
|
108
|
+
return 0
|
|
109
|
+
fi
|
|
110
|
+
UNSHARED_SETTINGS_KEYS=$(jq -rs --argjson keys "$_SETTINGS_OWNED_KEYS" '
|
|
111
|
+
.[0] as $local | .[1] as $head |
|
|
112
|
+
[$keys[] | select(($local[.] // null) != ($head[.] // null))] | join(", ")
|
|
113
|
+
' egregore.json "$tmp_head" 2>/dev/null || true)
|
|
114
|
+
rm -f "$tmp_head"
|
|
115
|
+
export UNSHARED_SETTINGS_KEYS
|
|
116
|
+
}
|
|
@@ -61,7 +61,7 @@ def _snapshot(runtime, actor, service, permission: Permission = Permission.READ)
|
|
|
61
61
|
|
|
62
62
|
|
|
63
63
|
def _resolve(runtime, actor, service, reference: str) -> IssueSnapshot:
|
|
64
|
-
rows, _ = _snapshot(runtime, actor)
|
|
64
|
+
rows, _ = _snapshot(runtime, actor, service)
|
|
65
65
|
return service.resolve(rows, reference)
|
|
66
66
|
|
|
67
67
|
|
|
@@ -72,7 +72,7 @@ def _write_result(issue: IssueSnapshot, receipt) -> int:
|
|
|
72
72
|
|
|
73
73
|
def _list(args):
|
|
74
74
|
_, runtime, actor, service = _context()
|
|
75
|
-
rows, _ = _snapshot(runtime, actor)
|
|
75
|
+
rows, _ = _snapshot(runtime, actor, service)
|
|
76
76
|
selected = rows if args.status == "all" else tuple(
|
|
77
77
|
row for row in rows if row.status == args.status
|
|
78
78
|
)
|
|
@@ -89,7 +89,7 @@ def _show(args):
|
|
|
89
89
|
|
|
90
90
|
def _search(args):
|
|
91
91
|
_, runtime, actor, service = _context()
|
|
92
|
-
rows, _ = _snapshot(runtime, actor)
|
|
92
|
+
rows, _ = _snapshot(runtime, actor, service)
|
|
93
93
|
terms = tuple(part for part in args.term.casefold().split() if part)
|
|
94
94
|
|
|
95
95
|
def matches(row: IssueSnapshot) -> bool:
|
|
@@ -24,6 +24,10 @@ FRAMEWORK_UPDATED_FILES=""
|
|
|
24
24
|
HEALTH_RETRIEVAL="skip"
|
|
25
25
|
SYNC_RECEIPT=""
|
|
26
26
|
|
|
27
|
+
# Settings drift healer + unshared-settings reporting (see the lib header).
|
|
28
|
+
# shellcheck source=bin/lib/settings-drift.sh
|
|
29
|
+
source "$SCRIPT_DIR/bin/lib/settings-drift.sh"
|
|
30
|
+
|
|
27
31
|
# The core repo's integration branch: "develop" unless egregore.json sets
|
|
28
32
|
# base_branch. session-start.sh resolves this before sourcing; resolve it here
|
|
29
33
|
# too so the lib stands alone for other callers.
|
|
@@ -324,8 +328,14 @@ setup_develop() {
|
|
|
324
328
|
return 1
|
|
325
329
|
fi
|
|
326
330
|
else
|
|
327
|
-
# Already on it — fetch couldn't update a checked-out branch, so pull
|
|
331
|
+
# Already on it — fetch couldn't update a checked-out branch, so pull.
|
|
332
|
+
# Launcher/installer settings drift blocks this merge and the refusal
|
|
333
|
+
# is otherwise silent; heal it first, re-apply the launcher's intent
|
|
334
|
+
# after (bin/lib/settings-drift.sh).
|
|
335
|
+
settings_drift_heal
|
|
328
336
|
git merge --ff-only "origin/$BASE_BRANCH" --quiet 2>/dev/null || true
|
|
337
|
+
settings_drift_reapply
|
|
338
|
+
settings_drift_report
|
|
329
339
|
fi
|
|
330
340
|
|
|
331
341
|
# Never report the intended branch as the actual branch. A checkout can fail
|
|
@@ -95,6 +95,14 @@ if [ -n "$SAVED_BRANCH" ]; then
|
|
|
95
95
|
echo " ✓ Auto-saved uncommitted work on $SAVED_BRANCH"
|
|
96
96
|
fi
|
|
97
97
|
|
|
98
|
+
# --- Unshared org-settings drift (launcher writes not yet shared) ---
|
|
99
|
+
if [ "${SETTINGS_ADAPTER_RESTORED:-false}" = "true" ]; then
|
|
100
|
+
echo " ◇ restored org .claude/settings.json — a generated adapter had replaced it locally"
|
|
101
|
+
fi
|
|
102
|
+
if [ -n "${UNSHARED_SETTINGS_KEYS:-}" ]; then
|
|
103
|
+
echo " ◇ org settings changed on this machine (${UNSHARED_SETTINGS_KEYS}) — say \"share settings\" to make them org-wide"
|
|
104
|
+
fi
|
|
105
|
+
|
|
98
106
|
# --- Footer: health + repos + memory (tertiary) ---
|
|
99
107
|
echo "$SEPARATOR"
|
|
100
108
|
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# settings-drift.sh — heal launcher/installer settings drift around the
|
|
3
|
+
# base-branch fast-forward, and surface unshared org-settings changes.
|
|
4
|
+
#
|
|
5
|
+
# Two org-config surfaces accumulate legitimate local modifications outside
|
|
6
|
+
# any session: .claude/settings.json (the installer's generated public-safe
|
|
7
|
+
# adapter can replace the org's richer committed file) and egregore.json
|
|
8
|
+
# (launcher settings verbs: posture, workflow, people removals). Local
|
|
9
|
+
# modifications to files a fast-forward also changes make git refuse the
|
|
10
|
+
# merge — and that refusal used to be silently swallowed, leaving the
|
|
11
|
+
# checkout behind with no explanation.
|
|
12
|
+
#
|
|
13
|
+
# Contract (sourced; caller provides $BASE_BRANCH and runs from the repo
|
|
14
|
+
# root):
|
|
15
|
+
# settings_drift_heal — before the ff-merge. Restores settings.json
|
|
16
|
+
# only when the local copy is a reduced
|
|
17
|
+
# generated adapter (its hook events and
|
|
18
|
+
# permission allows are a subset of the
|
|
19
|
+
# committed file's). Snapshots egregore.json
|
|
20
|
+
# and returns it to HEAD so the merge can run.
|
|
21
|
+
# settings_drift_reapply — after the ff-merge. Re-applies the snapshot's
|
|
22
|
+
# changed settings-owned keys on top of the
|
|
23
|
+
# incoming egregore.json.
|
|
24
|
+
# settings_drift_report — sets UNSHARED_SETTINGS_KEYS to the
|
|
25
|
+
# settings-owned keys where the working file
|
|
26
|
+
# differs from HEAD, for the greeting.
|
|
27
|
+
#
|
|
28
|
+
# Anything dirty beyond these two files is user work and is never touched.
|
|
29
|
+
|
|
30
|
+
_SETTINGS_OWNED_KEYS='["boundary","base_branch","people_removed","features","repos","admins","people"]'
|
|
31
|
+
_SETTINGS_DRIFT_SNAPSHOT=""
|
|
32
|
+
_SETTINGS_DRIFT_BASE=""
|
|
33
|
+
# Read by bin/lib/greeting.sh after git-sync completes.
|
|
34
|
+
export SETTINGS_ADAPTER_RESTORED="false"
|
|
35
|
+
export UNSHARED_SETTINGS_KEYS=""
|
|
36
|
+
|
|
37
|
+
# The generated adapter only ever removes relative to the org file: fewer
|
|
38
|
+
# hook events, fewer permission allows. A local settings.json that carries
|
|
39
|
+
# anything the committed file lacks is user work and stays untouched.
|
|
40
|
+
_settings_is_reduced_adapter() {
|
|
41
|
+
local tmp_head
|
|
42
|
+
tmp_head=$(mktemp) || return 1
|
|
43
|
+
if ! git show "HEAD:.claude/settings.json" > "$tmp_head" 2>/dev/null; then
|
|
44
|
+
rm -f "$tmp_head"
|
|
45
|
+
return 1
|
|
46
|
+
fi
|
|
47
|
+
jq -es '
|
|
48
|
+
.[0] as $local | .[1] as $head |
|
|
49
|
+
($local != $head) and
|
|
50
|
+
((($local.hooks // {}) | keys) - (($head.hooks // {}) | keys) | length == 0) and
|
|
51
|
+
((($local.permissions.allow // []) - ($head.permissions.allow // [])) | length == 0)
|
|
52
|
+
' .claude/settings.json "$tmp_head" >/dev/null 2>&1
|
|
53
|
+
local verdict=$?
|
|
54
|
+
rm -f "$tmp_head"
|
|
55
|
+
return "$verdict"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
settings_drift_heal() {
|
|
59
|
+
git rev-parse -q --verify "origin/$BASE_BRANCH" >/dev/null 2>&1 || return 0
|
|
60
|
+
[ "$(git rev-parse HEAD 2>/dev/null)" = "$(git rev-parse "origin/$BASE_BRANCH" 2>/dev/null)" ] && return 0
|
|
61
|
+
|
|
62
|
+
if ! git diff --quiet -- .claude/settings.json 2>/dev/null; then
|
|
63
|
+
if _settings_is_reduced_adapter; then
|
|
64
|
+
git checkout -- .claude/settings.json 2>/dev/null && SETTINGS_ADAPTER_RESTORED="true"
|
|
65
|
+
fi
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
if ! git diff --quiet -- egregore.json 2>/dev/null; then
|
|
69
|
+
_SETTINGS_DRIFT_SNAPSHOT=$(cat egregore.json 2>/dev/null || true)
|
|
70
|
+
_SETTINGS_DRIFT_BASE=$(git show "HEAD:egregore.json" 2>/dev/null || true)
|
|
71
|
+
if [ -n "$_SETTINGS_DRIFT_SNAPSHOT" ] && [ -n "$_SETTINGS_DRIFT_BASE" ] \
|
|
72
|
+
&& printf '%s' "$_SETTINGS_DRIFT_SNAPSHOT" | jq -e . >/dev/null 2>&1; then
|
|
73
|
+
git checkout -- egregore.json 2>/dev/null || _SETTINGS_DRIFT_SNAPSHOT=""
|
|
74
|
+
else
|
|
75
|
+
_SETTINGS_DRIFT_SNAPSHOT=""
|
|
76
|
+
fi
|
|
77
|
+
fi
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
settings_drift_reapply() {
|
|
81
|
+
[ -n "$_SETTINGS_DRIFT_SNAPSHOT" ] || return 0
|
|
82
|
+
local tmp_local tmp_base merged
|
|
83
|
+
tmp_local=$(mktemp) && tmp_base=$(mktemp) || return 0
|
|
84
|
+
printf '%s' "$_SETTINGS_DRIFT_SNAPSHOT" > "$tmp_local"
|
|
85
|
+
printf '%s' "$_SETTINGS_DRIFT_BASE" > "$tmp_base"
|
|
86
|
+
merged=$(jq -s --argjson keys "$_SETTINGS_OWNED_KEYS" '
|
|
87
|
+
.[0] as $incoming | .[1] as $local | .[2] as $base |
|
|
88
|
+
reduce $keys[] as $k ($incoming;
|
|
89
|
+
if ($local[$k] // null) != ($base[$k] // null) then
|
|
90
|
+
if ($local | has($k)) then .[$k] = $local[$k] else del(.[$k]) end
|
|
91
|
+
else . end)
|
|
92
|
+
' egregore.json "$tmp_local" "$tmp_base" 2>/dev/null || true)
|
|
93
|
+
rm -f "$tmp_local" "$tmp_base"
|
|
94
|
+
if [ -n "$merged" ] && printf '%s' "$merged" | jq -e . >/dev/null 2>&1; then
|
|
95
|
+
printf '%s\n' "$merged" > egregore.json
|
|
96
|
+
fi
|
|
97
|
+
_SETTINGS_DRIFT_SNAPSHOT=""
|
|
98
|
+
_SETTINGS_DRIFT_BASE=""
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
settings_drift_report() {
|
|
102
|
+
UNSHARED_SETTINGS_KEYS=""
|
|
103
|
+
git diff --quiet -- egregore.json 2>/dev/null && return 0
|
|
104
|
+
local tmp_head
|
|
105
|
+
tmp_head=$(mktemp) || return 0
|
|
106
|
+
if ! git show "HEAD:egregore.json" > "$tmp_head" 2>/dev/null; then
|
|
107
|
+
rm -f "$tmp_head"
|
|
108
|
+
return 0
|
|
109
|
+
fi
|
|
110
|
+
UNSHARED_SETTINGS_KEYS=$(jq -rs --argjson keys "$_SETTINGS_OWNED_KEYS" '
|
|
111
|
+
.[0] as $local | .[1] as $head |
|
|
112
|
+
[$keys[] | select(($local[.] // null) != ($head[.] // null))] | join(", ")
|
|
113
|
+
' egregore.json "$tmp_head" 2>/dev/null || true)
|
|
114
|
+
rm -f "$tmp_head"
|
|
115
|
+
export UNSHARED_SETTINGS_KEYS
|
|
116
|
+
}
|
|
@@ -61,7 +61,7 @@ def _snapshot(runtime, actor, service, permission: Permission = Permission.READ)
|
|
|
61
61
|
|
|
62
62
|
|
|
63
63
|
def _resolve(runtime, actor, service, reference: str) -> IssueSnapshot:
|
|
64
|
-
rows, _ = _snapshot(runtime, actor)
|
|
64
|
+
rows, _ = _snapshot(runtime, actor, service)
|
|
65
65
|
return service.resolve(rows, reference)
|
|
66
66
|
|
|
67
67
|
|
|
@@ -72,7 +72,7 @@ def _write_result(issue: IssueSnapshot, receipt) -> int:
|
|
|
72
72
|
|
|
73
73
|
def _list(args):
|
|
74
74
|
_, runtime, actor, service = _context()
|
|
75
|
-
rows, _ = _snapshot(runtime, actor)
|
|
75
|
+
rows, _ = _snapshot(runtime, actor, service)
|
|
76
76
|
selected = rows if args.status == "all" else tuple(
|
|
77
77
|
row for row in rows if row.status == args.status
|
|
78
78
|
)
|
|
@@ -89,7 +89,7 @@ def _show(args):
|
|
|
89
89
|
|
|
90
90
|
def _search(args):
|
|
91
91
|
_, runtime, actor, service = _context()
|
|
92
|
-
rows, _ = _snapshot(runtime, actor)
|
|
92
|
+
rows, _ = _snapshot(runtime, actor, service)
|
|
93
93
|
terms = tuple(part for part in args.term.casefold().split() if part)
|
|
94
94
|
|
|
95
95
|
def matches(row: IssueSnapshot) -> bool:
|
|
@@ -24,6 +24,10 @@ FRAMEWORK_UPDATED_FILES=""
|
|
|
24
24
|
HEALTH_RETRIEVAL="skip"
|
|
25
25
|
SYNC_RECEIPT=""
|
|
26
26
|
|
|
27
|
+
# Settings drift healer + unshared-settings reporting (see the lib header).
|
|
28
|
+
# shellcheck source=bin/lib/settings-drift.sh
|
|
29
|
+
source "$SCRIPT_DIR/bin/lib/settings-drift.sh"
|
|
30
|
+
|
|
27
31
|
# The core repo's integration branch: "develop" unless egregore.json sets
|
|
28
32
|
# base_branch. session-start.sh resolves this before sourcing; resolve it here
|
|
29
33
|
# too so the lib stands alone for other callers.
|
|
@@ -324,8 +328,14 @@ setup_develop() {
|
|
|
324
328
|
return 1
|
|
325
329
|
fi
|
|
326
330
|
else
|
|
327
|
-
# Already on it — fetch couldn't update a checked-out branch, so pull
|
|
331
|
+
# Already on it — fetch couldn't update a checked-out branch, so pull.
|
|
332
|
+
# Launcher/installer settings drift blocks this merge and the refusal
|
|
333
|
+
# is otherwise silent; heal it first, re-apply the launcher's intent
|
|
334
|
+
# after (bin/lib/settings-drift.sh).
|
|
335
|
+
settings_drift_heal
|
|
328
336
|
git merge --ff-only "origin/$BASE_BRANCH" --quiet 2>/dev/null || true
|
|
337
|
+
settings_drift_reapply
|
|
338
|
+
settings_drift_report
|
|
329
339
|
fi
|
|
330
340
|
|
|
331
341
|
# Never report the intended branch as the actual branch. A checkout can fail
|
|
@@ -95,6 +95,14 @@ if [ -n "$SAVED_BRANCH" ]; then
|
|
|
95
95
|
echo " ✓ Auto-saved uncommitted work on $SAVED_BRANCH"
|
|
96
96
|
fi
|
|
97
97
|
|
|
98
|
+
# --- Unshared org-settings drift (launcher writes not yet shared) ---
|
|
99
|
+
if [ "${SETTINGS_ADAPTER_RESTORED:-false}" = "true" ]; then
|
|
100
|
+
echo " ◇ restored org .claude/settings.json — a generated adapter had replaced it locally"
|
|
101
|
+
fi
|
|
102
|
+
if [ -n "${UNSHARED_SETTINGS_KEYS:-}" ]; then
|
|
103
|
+
echo " ◇ org settings changed on this machine (${UNSHARED_SETTINGS_KEYS}) — say \"share settings\" to make them org-wide"
|
|
104
|
+
fi
|
|
105
|
+
|
|
98
106
|
# --- Footer: health + repos + memory (tertiary) ---
|
|
99
107
|
echo "$SEPARATOR"
|
|
100
108
|
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# settings-drift.sh — heal launcher/installer settings drift around the
|
|
3
|
+
# base-branch fast-forward, and surface unshared org-settings changes.
|
|
4
|
+
#
|
|
5
|
+
# Two org-config surfaces accumulate legitimate local modifications outside
|
|
6
|
+
# any session: .claude/settings.json (the installer's generated public-safe
|
|
7
|
+
# adapter can replace the org's richer committed file) and egregore.json
|
|
8
|
+
# (launcher settings verbs: posture, workflow, people removals). Local
|
|
9
|
+
# modifications to files a fast-forward also changes make git refuse the
|
|
10
|
+
# merge — and that refusal used to be silently swallowed, leaving the
|
|
11
|
+
# checkout behind with no explanation.
|
|
12
|
+
#
|
|
13
|
+
# Contract (sourced; caller provides $BASE_BRANCH and runs from the repo
|
|
14
|
+
# root):
|
|
15
|
+
# settings_drift_heal — before the ff-merge. Restores settings.json
|
|
16
|
+
# only when the local copy is a reduced
|
|
17
|
+
# generated adapter (its hook events and
|
|
18
|
+
# permission allows are a subset of the
|
|
19
|
+
# committed file's). Snapshots egregore.json
|
|
20
|
+
# and returns it to HEAD so the merge can run.
|
|
21
|
+
# settings_drift_reapply — after the ff-merge. Re-applies the snapshot's
|
|
22
|
+
# changed settings-owned keys on top of the
|
|
23
|
+
# incoming egregore.json.
|
|
24
|
+
# settings_drift_report — sets UNSHARED_SETTINGS_KEYS to the
|
|
25
|
+
# settings-owned keys where the working file
|
|
26
|
+
# differs from HEAD, for the greeting.
|
|
27
|
+
#
|
|
28
|
+
# Anything dirty beyond these two files is user work and is never touched.
|
|
29
|
+
|
|
30
|
+
_SETTINGS_OWNED_KEYS='["boundary","base_branch","people_removed","features","repos","admins","people"]'
|
|
31
|
+
_SETTINGS_DRIFT_SNAPSHOT=""
|
|
32
|
+
_SETTINGS_DRIFT_BASE=""
|
|
33
|
+
# Read by bin/lib/greeting.sh after git-sync completes.
|
|
34
|
+
export SETTINGS_ADAPTER_RESTORED="false"
|
|
35
|
+
export UNSHARED_SETTINGS_KEYS=""
|
|
36
|
+
|
|
37
|
+
# The generated adapter only ever removes relative to the org file: fewer
|
|
38
|
+
# hook events, fewer permission allows. A local settings.json that carries
|
|
39
|
+
# anything the committed file lacks is user work and stays untouched.
|
|
40
|
+
_settings_is_reduced_adapter() {
|
|
41
|
+
local tmp_head
|
|
42
|
+
tmp_head=$(mktemp) || return 1
|
|
43
|
+
if ! git show "HEAD:.claude/settings.json" > "$tmp_head" 2>/dev/null; then
|
|
44
|
+
rm -f "$tmp_head"
|
|
45
|
+
return 1
|
|
46
|
+
fi
|
|
47
|
+
jq -es '
|
|
48
|
+
.[0] as $local | .[1] as $head |
|
|
49
|
+
($local != $head) and
|
|
50
|
+
((($local.hooks // {}) | keys) - (($head.hooks // {}) | keys) | length == 0) and
|
|
51
|
+
((($local.permissions.allow // []) - ($head.permissions.allow // [])) | length == 0)
|
|
52
|
+
' .claude/settings.json "$tmp_head" >/dev/null 2>&1
|
|
53
|
+
local verdict=$?
|
|
54
|
+
rm -f "$tmp_head"
|
|
55
|
+
return "$verdict"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
settings_drift_heal() {
|
|
59
|
+
git rev-parse -q --verify "origin/$BASE_BRANCH" >/dev/null 2>&1 || return 0
|
|
60
|
+
[ "$(git rev-parse HEAD 2>/dev/null)" = "$(git rev-parse "origin/$BASE_BRANCH" 2>/dev/null)" ] && return 0
|
|
61
|
+
|
|
62
|
+
if ! git diff --quiet -- .claude/settings.json 2>/dev/null; then
|
|
63
|
+
if _settings_is_reduced_adapter; then
|
|
64
|
+
git checkout -- .claude/settings.json 2>/dev/null && SETTINGS_ADAPTER_RESTORED="true"
|
|
65
|
+
fi
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
if ! git diff --quiet -- egregore.json 2>/dev/null; then
|
|
69
|
+
_SETTINGS_DRIFT_SNAPSHOT=$(cat egregore.json 2>/dev/null || true)
|
|
70
|
+
_SETTINGS_DRIFT_BASE=$(git show "HEAD:egregore.json" 2>/dev/null || true)
|
|
71
|
+
if [ -n "$_SETTINGS_DRIFT_SNAPSHOT" ] && [ -n "$_SETTINGS_DRIFT_BASE" ] \
|
|
72
|
+
&& printf '%s' "$_SETTINGS_DRIFT_SNAPSHOT" | jq -e . >/dev/null 2>&1; then
|
|
73
|
+
git checkout -- egregore.json 2>/dev/null || _SETTINGS_DRIFT_SNAPSHOT=""
|
|
74
|
+
else
|
|
75
|
+
_SETTINGS_DRIFT_SNAPSHOT=""
|
|
76
|
+
fi
|
|
77
|
+
fi
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
settings_drift_reapply() {
|
|
81
|
+
[ -n "$_SETTINGS_DRIFT_SNAPSHOT" ] || return 0
|
|
82
|
+
local tmp_local tmp_base merged
|
|
83
|
+
tmp_local=$(mktemp) && tmp_base=$(mktemp) || return 0
|
|
84
|
+
printf '%s' "$_SETTINGS_DRIFT_SNAPSHOT" > "$tmp_local"
|
|
85
|
+
printf '%s' "$_SETTINGS_DRIFT_BASE" > "$tmp_base"
|
|
86
|
+
merged=$(jq -s --argjson keys "$_SETTINGS_OWNED_KEYS" '
|
|
87
|
+
.[0] as $incoming | .[1] as $local | .[2] as $base |
|
|
88
|
+
reduce $keys[] as $k ($incoming;
|
|
89
|
+
if ($local[$k] // null) != ($base[$k] // null) then
|
|
90
|
+
if ($local | has($k)) then .[$k] = $local[$k] else del(.[$k]) end
|
|
91
|
+
else . end)
|
|
92
|
+
' egregore.json "$tmp_local" "$tmp_base" 2>/dev/null || true)
|
|
93
|
+
rm -f "$tmp_local" "$tmp_base"
|
|
94
|
+
if [ -n "$merged" ] && printf '%s' "$merged" | jq -e . >/dev/null 2>&1; then
|
|
95
|
+
printf '%s\n' "$merged" > egregore.json
|
|
96
|
+
fi
|
|
97
|
+
_SETTINGS_DRIFT_SNAPSHOT=""
|
|
98
|
+
_SETTINGS_DRIFT_BASE=""
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
settings_drift_report() {
|
|
102
|
+
UNSHARED_SETTINGS_KEYS=""
|
|
103
|
+
git diff --quiet -- egregore.json 2>/dev/null && return 0
|
|
104
|
+
local tmp_head
|
|
105
|
+
tmp_head=$(mktemp) || return 0
|
|
106
|
+
if ! git show "HEAD:egregore.json" > "$tmp_head" 2>/dev/null; then
|
|
107
|
+
rm -f "$tmp_head"
|
|
108
|
+
return 0
|
|
109
|
+
fi
|
|
110
|
+
UNSHARED_SETTINGS_KEYS=$(jq -rs --argjson keys "$_SETTINGS_OWNED_KEYS" '
|
|
111
|
+
.[0] as $local | .[1] as $head |
|
|
112
|
+
[$keys[] | select(($local[.] // null) != ($head[.] // null))] | join(", ")
|
|
113
|
+
' egregore.json "$tmp_head" 2>/dev/null || true)
|
|
114
|
+
rm -f "$tmp_head"
|
|
115
|
+
export UNSHARED_SETTINGS_KEYS
|
|
116
|
+
}
|
|
@@ -61,7 +61,7 @@ def _snapshot(runtime, actor, service, permission: Permission = Permission.READ)
|
|
|
61
61
|
|
|
62
62
|
|
|
63
63
|
def _resolve(runtime, actor, service, reference: str) -> IssueSnapshot:
|
|
64
|
-
rows, _ = _snapshot(runtime, actor)
|
|
64
|
+
rows, _ = _snapshot(runtime, actor, service)
|
|
65
65
|
return service.resolve(rows, reference)
|
|
66
66
|
|
|
67
67
|
|
|
@@ -72,7 +72,7 @@ def _write_result(issue: IssueSnapshot, receipt) -> int:
|
|
|
72
72
|
|
|
73
73
|
def _list(args):
|
|
74
74
|
_, runtime, actor, service = _context()
|
|
75
|
-
rows, _ = _snapshot(runtime, actor)
|
|
75
|
+
rows, _ = _snapshot(runtime, actor, service)
|
|
76
76
|
selected = rows if args.status == "all" else tuple(
|
|
77
77
|
row for row in rows if row.status == args.status
|
|
78
78
|
)
|
|
@@ -89,7 +89,7 @@ def _show(args):
|
|
|
89
89
|
|
|
90
90
|
def _search(args):
|
|
91
91
|
_, runtime, actor, service = _context()
|
|
92
|
-
rows, _ = _snapshot(runtime, actor)
|
|
92
|
+
rows, _ = _snapshot(runtime, actor, service)
|
|
93
93
|
terms = tuple(part for part in args.term.casefold().split() if part)
|
|
94
94
|
|
|
95
95
|
def matches(row: IssueSnapshot) -> bool:
|
package/runtime/pi/manifest.json
CHANGED
|
@@ -24,6 +24,10 @@ FRAMEWORK_UPDATED_FILES=""
|
|
|
24
24
|
HEALTH_RETRIEVAL="skip"
|
|
25
25
|
SYNC_RECEIPT=""
|
|
26
26
|
|
|
27
|
+
# Settings drift healer + unshared-settings reporting (see the lib header).
|
|
28
|
+
# shellcheck source=bin/lib/settings-drift.sh
|
|
29
|
+
source "$SCRIPT_DIR/bin/lib/settings-drift.sh"
|
|
30
|
+
|
|
27
31
|
# The core repo's integration branch: "develop" unless egregore.json sets
|
|
28
32
|
# base_branch. session-start.sh resolves this before sourcing; resolve it here
|
|
29
33
|
# too so the lib stands alone for other callers.
|
|
@@ -324,8 +328,14 @@ setup_develop() {
|
|
|
324
328
|
return 1
|
|
325
329
|
fi
|
|
326
330
|
else
|
|
327
|
-
# Already on it — fetch couldn't update a checked-out branch, so pull
|
|
331
|
+
# Already on it — fetch couldn't update a checked-out branch, so pull.
|
|
332
|
+
# Launcher/installer settings drift blocks this merge and the refusal
|
|
333
|
+
# is otherwise silent; heal it first, re-apply the launcher's intent
|
|
334
|
+
# after (bin/lib/settings-drift.sh).
|
|
335
|
+
settings_drift_heal
|
|
328
336
|
git merge --ff-only "origin/$BASE_BRANCH" --quiet 2>/dev/null || true
|
|
337
|
+
settings_drift_reapply
|
|
338
|
+
settings_drift_report
|
|
329
339
|
fi
|
|
330
340
|
|
|
331
341
|
# Never report the intended branch as the actual branch. A checkout can fail
|
|
@@ -95,6 +95,14 @@ if [ -n "$SAVED_BRANCH" ]; then
|
|
|
95
95
|
echo " ✓ Auto-saved uncommitted work on $SAVED_BRANCH"
|
|
96
96
|
fi
|
|
97
97
|
|
|
98
|
+
# --- Unshared org-settings drift (launcher writes not yet shared) ---
|
|
99
|
+
if [ "${SETTINGS_ADAPTER_RESTORED:-false}" = "true" ]; then
|
|
100
|
+
echo " ◇ restored org .claude/settings.json — a generated adapter had replaced it locally"
|
|
101
|
+
fi
|
|
102
|
+
if [ -n "${UNSHARED_SETTINGS_KEYS:-}" ]; then
|
|
103
|
+
echo " ◇ org settings changed on this machine (${UNSHARED_SETTINGS_KEYS}) — say \"share settings\" to make them org-wide"
|
|
104
|
+
fi
|
|
105
|
+
|
|
98
106
|
# --- Footer: health + repos + memory (tertiary) ---
|
|
99
107
|
echo "$SEPARATOR"
|
|
100
108
|
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# settings-drift.sh — heal launcher/installer settings drift around the
|
|
3
|
+
# base-branch fast-forward, and surface unshared org-settings changes.
|
|
4
|
+
#
|
|
5
|
+
# Two org-config surfaces accumulate legitimate local modifications outside
|
|
6
|
+
# any session: .claude/settings.json (the installer's generated public-safe
|
|
7
|
+
# adapter can replace the org's richer committed file) and egregore.json
|
|
8
|
+
# (launcher settings verbs: posture, workflow, people removals). Local
|
|
9
|
+
# modifications to files a fast-forward also changes make git refuse the
|
|
10
|
+
# merge — and that refusal used to be silently swallowed, leaving the
|
|
11
|
+
# checkout behind with no explanation.
|
|
12
|
+
#
|
|
13
|
+
# Contract (sourced; caller provides $BASE_BRANCH and runs from the repo
|
|
14
|
+
# root):
|
|
15
|
+
# settings_drift_heal — before the ff-merge. Restores settings.json
|
|
16
|
+
# only when the local copy is a reduced
|
|
17
|
+
# generated adapter (its hook events and
|
|
18
|
+
# permission allows are a subset of the
|
|
19
|
+
# committed file's). Snapshots egregore.json
|
|
20
|
+
# and returns it to HEAD so the merge can run.
|
|
21
|
+
# settings_drift_reapply — after the ff-merge. Re-applies the snapshot's
|
|
22
|
+
# changed settings-owned keys on top of the
|
|
23
|
+
# incoming egregore.json.
|
|
24
|
+
# settings_drift_report — sets UNSHARED_SETTINGS_KEYS to the
|
|
25
|
+
# settings-owned keys where the working file
|
|
26
|
+
# differs from HEAD, for the greeting.
|
|
27
|
+
#
|
|
28
|
+
# Anything dirty beyond these two files is user work and is never touched.
|
|
29
|
+
|
|
30
|
+
_SETTINGS_OWNED_KEYS='["boundary","base_branch","people_removed","features","repos","admins","people"]'
|
|
31
|
+
_SETTINGS_DRIFT_SNAPSHOT=""
|
|
32
|
+
_SETTINGS_DRIFT_BASE=""
|
|
33
|
+
# Read by bin/lib/greeting.sh after git-sync completes.
|
|
34
|
+
export SETTINGS_ADAPTER_RESTORED="false"
|
|
35
|
+
export UNSHARED_SETTINGS_KEYS=""
|
|
36
|
+
|
|
37
|
+
# The generated adapter only ever removes relative to the org file: fewer
|
|
38
|
+
# hook events, fewer permission allows. A local settings.json that carries
|
|
39
|
+
# anything the committed file lacks is user work and stays untouched.
|
|
40
|
+
_settings_is_reduced_adapter() {
|
|
41
|
+
local tmp_head
|
|
42
|
+
tmp_head=$(mktemp) || return 1
|
|
43
|
+
if ! git show "HEAD:.claude/settings.json" > "$tmp_head" 2>/dev/null; then
|
|
44
|
+
rm -f "$tmp_head"
|
|
45
|
+
return 1
|
|
46
|
+
fi
|
|
47
|
+
jq -es '
|
|
48
|
+
.[0] as $local | .[1] as $head |
|
|
49
|
+
($local != $head) and
|
|
50
|
+
((($local.hooks // {}) | keys) - (($head.hooks // {}) | keys) | length == 0) and
|
|
51
|
+
((($local.permissions.allow // []) - ($head.permissions.allow // [])) | length == 0)
|
|
52
|
+
' .claude/settings.json "$tmp_head" >/dev/null 2>&1
|
|
53
|
+
local verdict=$?
|
|
54
|
+
rm -f "$tmp_head"
|
|
55
|
+
return "$verdict"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
settings_drift_heal() {
|
|
59
|
+
git rev-parse -q --verify "origin/$BASE_BRANCH" >/dev/null 2>&1 || return 0
|
|
60
|
+
[ "$(git rev-parse HEAD 2>/dev/null)" = "$(git rev-parse "origin/$BASE_BRANCH" 2>/dev/null)" ] && return 0
|
|
61
|
+
|
|
62
|
+
if ! git diff --quiet -- .claude/settings.json 2>/dev/null; then
|
|
63
|
+
if _settings_is_reduced_adapter; then
|
|
64
|
+
git checkout -- .claude/settings.json 2>/dev/null && SETTINGS_ADAPTER_RESTORED="true"
|
|
65
|
+
fi
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
if ! git diff --quiet -- egregore.json 2>/dev/null; then
|
|
69
|
+
_SETTINGS_DRIFT_SNAPSHOT=$(cat egregore.json 2>/dev/null || true)
|
|
70
|
+
_SETTINGS_DRIFT_BASE=$(git show "HEAD:egregore.json" 2>/dev/null || true)
|
|
71
|
+
if [ -n "$_SETTINGS_DRIFT_SNAPSHOT" ] && [ -n "$_SETTINGS_DRIFT_BASE" ] \
|
|
72
|
+
&& printf '%s' "$_SETTINGS_DRIFT_SNAPSHOT" | jq -e . >/dev/null 2>&1; then
|
|
73
|
+
git checkout -- egregore.json 2>/dev/null || _SETTINGS_DRIFT_SNAPSHOT=""
|
|
74
|
+
else
|
|
75
|
+
_SETTINGS_DRIFT_SNAPSHOT=""
|
|
76
|
+
fi
|
|
77
|
+
fi
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
settings_drift_reapply() {
|
|
81
|
+
[ -n "$_SETTINGS_DRIFT_SNAPSHOT" ] || return 0
|
|
82
|
+
local tmp_local tmp_base merged
|
|
83
|
+
tmp_local=$(mktemp) && tmp_base=$(mktemp) || return 0
|
|
84
|
+
printf '%s' "$_SETTINGS_DRIFT_SNAPSHOT" > "$tmp_local"
|
|
85
|
+
printf '%s' "$_SETTINGS_DRIFT_BASE" > "$tmp_base"
|
|
86
|
+
merged=$(jq -s --argjson keys "$_SETTINGS_OWNED_KEYS" '
|
|
87
|
+
.[0] as $incoming | .[1] as $local | .[2] as $base |
|
|
88
|
+
reduce $keys[] as $k ($incoming;
|
|
89
|
+
if ($local[$k] // null) != ($base[$k] // null) then
|
|
90
|
+
if ($local | has($k)) then .[$k] = $local[$k] else del(.[$k]) end
|
|
91
|
+
else . end)
|
|
92
|
+
' egregore.json "$tmp_local" "$tmp_base" 2>/dev/null || true)
|
|
93
|
+
rm -f "$tmp_local" "$tmp_base"
|
|
94
|
+
if [ -n "$merged" ] && printf '%s' "$merged" | jq -e . >/dev/null 2>&1; then
|
|
95
|
+
printf '%s\n' "$merged" > egregore.json
|
|
96
|
+
fi
|
|
97
|
+
_SETTINGS_DRIFT_SNAPSHOT=""
|
|
98
|
+
_SETTINGS_DRIFT_BASE=""
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
settings_drift_report() {
|
|
102
|
+
UNSHARED_SETTINGS_KEYS=""
|
|
103
|
+
git diff --quiet -- egregore.json 2>/dev/null && return 0
|
|
104
|
+
local tmp_head
|
|
105
|
+
tmp_head=$(mktemp) || return 0
|
|
106
|
+
if ! git show "HEAD:egregore.json" > "$tmp_head" 2>/dev/null; then
|
|
107
|
+
rm -f "$tmp_head"
|
|
108
|
+
return 0
|
|
109
|
+
fi
|
|
110
|
+
UNSHARED_SETTINGS_KEYS=$(jq -rs --argjson keys "$_SETTINGS_OWNED_KEYS" '
|
|
111
|
+
.[0] as $local | .[1] as $head |
|
|
112
|
+
[$keys[] | select(($local[.] // null) != ($head[.] // null))] | join(", ")
|
|
113
|
+
' egregore.json "$tmp_head" 2>/dev/null || true)
|
|
114
|
+
rm -f "$tmp_head"
|
|
115
|
+
export UNSHARED_SETTINGS_KEYS
|
|
116
|
+
}
|
|
@@ -61,7 +61,7 @@ def _snapshot(runtime, actor, service, permission: Permission = Permission.READ)
|
|
|
61
61
|
|
|
62
62
|
|
|
63
63
|
def _resolve(runtime, actor, service, reference: str) -> IssueSnapshot:
|
|
64
|
-
rows, _ = _snapshot(runtime, actor)
|
|
64
|
+
rows, _ = _snapshot(runtime, actor, service)
|
|
65
65
|
return service.resolve(rows, reference)
|
|
66
66
|
|
|
67
67
|
|
|
@@ -72,7 +72,7 @@ def _write_result(issue: IssueSnapshot, receipt) -> int:
|
|
|
72
72
|
|
|
73
73
|
def _list(args):
|
|
74
74
|
_, runtime, actor, service = _context()
|
|
75
|
-
rows, _ = _snapshot(runtime, actor)
|
|
75
|
+
rows, _ = _snapshot(runtime, actor, service)
|
|
76
76
|
selected = rows if args.status == "all" else tuple(
|
|
77
77
|
row for row in rows if row.status == args.status
|
|
78
78
|
)
|
|
@@ -89,7 +89,7 @@ def _show(args):
|
|
|
89
89
|
|
|
90
90
|
def _search(args):
|
|
91
91
|
_, runtime, actor, service = _context()
|
|
92
|
-
rows, _ = _snapshot(runtime, actor)
|
|
92
|
+
rows, _ = _snapshot(runtime, actor, service)
|
|
93
93
|
terms = tuple(part for part in args.term.casefold().split() if part)
|
|
94
94
|
|
|
95
95
|
def matches(row: IssueSnapshot) -> bool:
|