gator-command 1.2.2__tar.gz → 1.3.0__tar.gz
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.
- {gator_command-1.2.2/src/gator_command.egg-info → gator_command-1.3.0}/PKG-INFO +1 -1
- {gator_command-1.2.2 → gator_command-1.3.0}/pyproject.toml +1 -1
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/gator-deploy.py +1 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/gator-session-aggregator.py +79 -26
- {gator_command-1.2.2/src/gator_command/templates/gator-starter → gator_command-1.3.0/src/gator_command}/scripts/gator-update.py +200 -103
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/gatorize.py +104 -20
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/scripts/gator-pre-commit.py +57 -7
- gator_command-1.3.0/src/gator_command/templates/gator-starter/scripts/gator-session-start.py +273 -0
- gator_command-1.3.0/src/gator_command/templates/gator-starter/vendor-hooks/claude-settings.json +15 -0
- gator_command-1.3.0/src/gator_command/templates/gator-starter/vendor-hooks/codex-hooks.json +15 -0
- gator_command-1.3.0/src/gator_command/templates/gator-starter/vendor-hooks/gemini-settings.json +15 -0
- {gator_command-1.2.2 → gator_command-1.3.0/src/gator_command.egg-info}/PKG-INFO +1 -1
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command.egg-info/SOURCES.txt +7 -1
- {gator_command-1.2.2 → gator_command-1.3.0}/tests/test_pre_commit.py +135 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/tests/test_session_aggregator.py +105 -0
- gator_command-1.3.0/tests/test_session_start.py +360 -0
- gator_command-1.3.0/tests/test_vendor_hooks.py +292 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/LICENSE +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/PYPI_README.md +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/README.md +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/setup.cfg +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/__init__.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/cli.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/crawler.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/dashboard/dashboard.css +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/dashboard/dashboard.html +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/dashboard/dashboard.js +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/dashboard/views/audit.js +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/dashboard/views/fleet.js +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/dashboard/views/repo.js +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/dashboard/views/settings.js +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/dashboard/views/updates.js +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/enforcer-prompt.md +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/extract-claude-sessions.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/extract-codex-sessions.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/extract-gemini-sessions.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/gator-audit-renderers.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/gator-audit.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/gator-charter-draft.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/gator-charter-lint.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/gator-charter-verify.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/gator-dashboard.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/gator-drift.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/gator-enforce.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/gator-fleet-intel.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/gator-fleet-report.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/gator-init-command-post.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/gator-init.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/gator-machine-id.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/gator-policy-status.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/gator-pulse.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/gator-repo-status.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/gator-session-common.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/gator-session-sink.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/gator-sessions.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/gator-version.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/gator_core.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/gator_remote.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/gator_runtime.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/gatorize-actions.sh +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/gatorize-lib.sh +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/gatorize-post.sh +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/gatorize.sh +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/generate_markdown.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/generate_wiki.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/graph_health.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/memex-lint.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/memex-lint.sh +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/memex.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/memex_formatters.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/memex_state.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/spawn.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/blueprints/README.md +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/charterignore +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/charters/README.md +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/charters/_template.md +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/commands/commit.md +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/commands/init.md +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/commands/update.md +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/constitution.md +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/field-guides/README.md +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/gator-start-up.md +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/procedures/charter-alignment.md +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/procedures/enforcer-review.md +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/procedures/field-guide-generation.md +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/procedures/knowledge-capture.md +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/procedures/significance-check.md +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/reference-notes/concierge-responses.md +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/reference-notes/dangerous-patterns.md +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/reference-notes/dashboard-operations.md +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/reference-notes/enforcer-configuration.md +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/reference-notes/example-project.md +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/reference-notes/failure-modes-and-self-correction.md +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/reference-notes/git-workflow.md +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/reference-notes/identity-and-ownership.md +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/reference-notes/refactor-approach.md +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/reference-notes/what-gator-requires-from-a-model.md +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/reference-notes/why-navigation-coding-feels-different.md +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/reference-notes/workflow-profiles.md +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/scripts/__pycache__/enforcer-review.cpython-313.pyc +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/scripts/__pycache__/gator-approve.cpython-313.pyc +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/scripts/__pycache__/gator-init.cpython-313.pyc +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/scripts/__pycache__/gator-pre-commit.cpython-313.pyc +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/scripts/__pycache__/gator-update.cpython-313.pyc +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/scripts/enforcer-prompt.md +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/scripts/enforcer-review.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/scripts/gator-approve.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/scripts/gator-enforce.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/scripts/gator-init.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/scripts/gator-pulse.py +0 -0
- {gator_command-1.2.2/src/gator_command → gator_command-1.3.0/src/gator_command/templates/gator-starter}/scripts/gator-update.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/scripts/gator-version.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/scripts/gator_core.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/scripts/hooks/__pycache__/commit-msgcpython-313.pyc +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/scripts/hooks/__pycache__/post-commitcpython-313.pyc +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/scripts/hooks/__pycache__/pre-commitcpython-313.pyc +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/scripts/hooks/commit-msg +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/scripts/hooks/post-commit +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/scripts/hooks/pre-commit +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/sessions/.gitignore +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/vault/.gitkeep +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/templates/gator-starter/whiteboard.md +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command.egg-info/dependency_links.txt +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command.egg-info/entry_points.txt +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command.egg-info/top_level.txt +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/tests/test_audit.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/tests/test_audit_integration.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/tests/test_charter_lint.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/tests/test_dashboard_sessions.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/tests/test_deploy.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/tests/test_drift.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/tests/test_fleet_intel.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/tests/test_fleet_report.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/tests/test_gator_core.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/tests/test_gatorize.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/tests/test_hooks.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/tests/test_init.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/tests/test_memex_state.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/tests/test_packaging.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/tests/test_remote.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/tests/test_session_sink.py +0 -0
- {gator_command-1.2.2 → gator_command-1.3.0}/tests/test_sessions.py +0 -0
{gator_command-1.2.2 → gator_command-1.3.0}/src/gator_command/scripts/gator-session-aggregator.py
RENAMED
|
@@ -8,7 +8,7 @@ entry point. Dashboard and CLI scripts import this.
|
|
|
8
8
|
|
|
9
9
|
@reads: .gator/session-snippets/*.json (per repo)
|
|
10
10
|
@reads: ~/.gator/dashboard-repos.json (fleet queries)
|
|
11
|
-
@writes: ~/.gator/sessions/<path-hash>/<
|
|
11
|
+
@writes: ~/.gator/sessions/<path-hash>/<esk-hash>.json (cached summaries)
|
|
12
12
|
@writes: ~/.gator/sessions/<path-hash>/_repo.json (repo metadata)
|
|
13
13
|
"""
|
|
14
14
|
|
|
@@ -33,6 +33,28 @@ SIGNIFICANCE_ORDER = {"routine": 0, "minor": 1, "high": 2}
|
|
|
33
33
|
SKIP_CHANGE_TYPES_FOR_GOAL = {"release", "merge", "cleanup"}
|
|
34
34
|
|
|
35
35
|
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
# Session Identity
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
def effective_session_key(snippet):
|
|
41
|
+
"""Return the canonical grouping key for a snippet.
|
|
42
|
+
|
|
43
|
+
Uses vendor session identity when available, falls back to legacy
|
|
44
|
+
Gator session_id. The returned string is used for aggregation grouping,
|
|
45
|
+
cache filenames, and fingerprint lookups.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
"group:<repo>:<session_group_key>" when session_group_key is truthy
|
|
49
|
+
"legacy:<repo>:<session_id>" otherwise
|
|
50
|
+
"""
|
|
51
|
+
repo = snippet.get("repo", "")
|
|
52
|
+
sgk = snippet.get("session_group_key")
|
|
53
|
+
if sgk:
|
|
54
|
+
return f"group:{repo}:{sgk}"
|
|
55
|
+
return f"legacy:{repo}:{snippet.get('session_id', '')}"
|
|
56
|
+
|
|
57
|
+
|
|
36
58
|
# ---------------------------------------------------------------------------
|
|
37
59
|
# 1a. Snippet Reader
|
|
38
60
|
# ---------------------------------------------------------------------------
|
|
@@ -75,7 +97,11 @@ def read_snippets(repo_path):
|
|
|
75
97
|
# ---------------------------------------------------------------------------
|
|
76
98
|
|
|
77
99
|
def aggregate_sessions(snippets, repo_path):
|
|
78
|
-
"""Group snippets by (
|
|
100
|
+
"""Group snippets by effective_session_key() and aggregate into summaries.
|
|
101
|
+
|
|
102
|
+
Uses vendor session_group_key when present, falls back to legacy
|
|
103
|
+
Gator session_id. Snippets with different effective keys are never
|
|
104
|
+
merged, even if timestamps or transcript IDs look similar.
|
|
79
105
|
|
|
80
106
|
Args:
|
|
81
107
|
snippets: list of parsed snippet dicts (the "data" from SnippetRecords)
|
|
@@ -83,10 +109,10 @@ def aggregate_sessions(snippets, repo_path):
|
|
|
83
109
|
|
|
84
110
|
Returns list of summary dicts matching gator-session-summary-v1 schema.
|
|
85
111
|
"""
|
|
86
|
-
# Group by
|
|
112
|
+
# Group by effective_session_key
|
|
87
113
|
groups = {}
|
|
88
114
|
for s in snippets:
|
|
89
|
-
key = (s
|
|
115
|
+
key = effective_session_key(s)
|
|
90
116
|
if key not in groups:
|
|
91
117
|
groups[key] = []
|
|
92
118
|
groups[key].append(s)
|
|
@@ -94,7 +120,7 @@ def aggregate_sessions(snippets, repo_path):
|
|
|
94
120
|
repo_key = session_cache_key(repo_path)
|
|
95
121
|
summaries = []
|
|
96
122
|
|
|
97
|
-
for
|
|
123
|
+
for esk, group in groups.items():
|
|
98
124
|
# Sort within group
|
|
99
125
|
group.sort(key=lambda s: (
|
|
100
126
|
s.get("started_at", ""),
|
|
@@ -110,6 +136,7 @@ def aggregate_sessions(snippets, repo_path):
|
|
|
110
136
|
all_intents = []
|
|
111
137
|
all_notes = []
|
|
112
138
|
all_change_types = set()
|
|
139
|
+
all_models = set()
|
|
113
140
|
max_sig = "routine"
|
|
114
141
|
architect = ""
|
|
115
142
|
transcript_session_id = None
|
|
@@ -150,6 +177,11 @@ def aggregate_sessions(snippets, repo_path):
|
|
|
150
177
|
if ct:
|
|
151
178
|
all_change_types.add(ct)
|
|
152
179
|
|
|
180
|
+
# Models — distinct
|
|
181
|
+
model = s.get("model_inferred", "")
|
|
182
|
+
if model:
|
|
183
|
+
all_models.add(model)
|
|
184
|
+
|
|
153
185
|
# Max significance
|
|
154
186
|
sig = s.get("significance", "routine")
|
|
155
187
|
if SIGNIFICANCE_ORDER.get(sig, 0) > SIGNIFICANCE_ORDER.get(max_sig, 0):
|
|
@@ -165,14 +197,24 @@ def aggregate_sessions(snippets, repo_path):
|
|
|
165
197
|
if not transcript_ref:
|
|
166
198
|
transcript_ref = s.get("transcript_ref")
|
|
167
199
|
|
|
200
|
+
# session_group_key: first non-null from the group (all should agree)
|
|
201
|
+
session_group_key = None
|
|
202
|
+
for s in group:
|
|
203
|
+
sgk = s.get("session_group_key")
|
|
204
|
+
if sgk:
|
|
205
|
+
session_group_key = sgk
|
|
206
|
+
break
|
|
207
|
+
|
|
168
208
|
summary = {
|
|
169
209
|
"schema": "gator-session-summary-v1",
|
|
170
|
-
"session_id": session_id,
|
|
171
|
-
"
|
|
210
|
+
"session_id": first.get("session_id", ""),
|
|
211
|
+
"session_group_key": session_group_key,
|
|
212
|
+
"repo": first.get("repo", ""),
|
|
172
213
|
"repo_key": repo_key,
|
|
173
214
|
"branch": first.get("branch", ""),
|
|
174
215
|
"vendor": first.get("vendor_inferred", ""),
|
|
175
216
|
"model": first.get("model_inferred", ""),
|
|
217
|
+
"models": sorted(all_models),
|
|
176
218
|
"agent": first.get("agent", ""),
|
|
177
219
|
"architect": architect,
|
|
178
220
|
"started_at": min(s.get("started_at", "") for s in group),
|
|
@@ -273,21 +315,32 @@ def write_repo_metadata(cache_path, repo_name, repo_path):
|
|
|
273
315
|
_atomic_write(meta_path, json.dumps(meta, indent=2) + "\n")
|
|
274
316
|
|
|
275
317
|
|
|
276
|
-
def
|
|
277
|
-
"""
|
|
318
|
+
def _esk_from_summary(summary):
|
|
319
|
+
"""Reconstruct the effective session key from a summary dict.
|
|
320
|
+
|
|
321
|
+
Mirrors effective_session_key() but reads from summary fields
|
|
322
|
+
instead of snippet fields.
|
|
323
|
+
"""
|
|
324
|
+
repo = summary.get("repo", "")
|
|
325
|
+
sgk = summary.get("session_group_key")
|
|
326
|
+
if sgk:
|
|
327
|
+
return f"group:{repo}:{sgk}"
|
|
328
|
+
return f"legacy:{repo}:{summary.get('session_id', '')}"
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _summary_cache_filename(esk):
|
|
332
|
+
"""Build a cache filename from the effective session key.
|
|
278
333
|
|
|
279
|
-
|
|
280
|
-
|
|
334
|
+
Hashes the full effective_session_key string to produce a stable,
|
|
335
|
+
filesystem-safe filename. The key already encodes repo + identity.
|
|
281
336
|
"""
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
return f"{repo_prefix}-{session_id}.json"
|
|
285
|
-
return f"{session_id}.json"
|
|
337
|
+
h = hashlib.sha256(esk.encode()).hexdigest()[:16]
|
|
338
|
+
return f"{h}.json"
|
|
286
339
|
|
|
287
340
|
|
|
288
|
-
def read_cached_summary(cache_path,
|
|
341
|
+
def read_cached_summary(cache_path, esk):
|
|
289
342
|
"""Read a cached session summary, or return None."""
|
|
290
|
-
summary_path = cache_path / _summary_cache_filename(
|
|
343
|
+
summary_path = cache_path / _summary_cache_filename(esk)
|
|
291
344
|
if not summary_path.exists():
|
|
292
345
|
return None
|
|
293
346
|
try:
|
|
@@ -296,10 +349,10 @@ def read_cached_summary(cache_path, repo, session_id):
|
|
|
296
349
|
return None
|
|
297
350
|
|
|
298
351
|
|
|
299
|
-
def write_cached_summary(cache_path, summary):
|
|
352
|
+
def write_cached_summary(cache_path, summary, esk):
|
|
300
353
|
"""Write a session summary to cache."""
|
|
301
354
|
cache_path.mkdir(parents=True, exist_ok=True)
|
|
302
|
-
filename = _summary_cache_filename(
|
|
355
|
+
filename = _summary_cache_filename(esk)
|
|
303
356
|
summary_path = cache_path / filename
|
|
304
357
|
_atomic_write(summary_path, json.dumps(summary, indent=2) + "\n")
|
|
305
358
|
|
|
@@ -349,10 +402,10 @@ def get_session_summaries(repo_path, force_refresh=False):
|
|
|
349
402
|
snippets_data = [r["data"] for r in records]
|
|
350
403
|
sessions = aggregate_sessions(snippets_data, repo_path)
|
|
351
404
|
|
|
352
|
-
# Build a lookup:
|
|
405
|
+
# Build a lookup: effective_session_key -> list of records (for fingerprinting)
|
|
353
406
|
session_records = {}
|
|
354
407
|
for r in records:
|
|
355
|
-
key = (r["data"]
|
|
408
|
+
key = effective_session_key(r["data"])
|
|
356
409
|
if key not in session_records:
|
|
357
410
|
session_records[key] = []
|
|
358
411
|
session_records[key].append(r)
|
|
@@ -362,13 +415,13 @@ def get_session_summaries(repo_path, force_refresh=False):
|
|
|
362
415
|
wrote_any = False
|
|
363
416
|
|
|
364
417
|
for summary in sessions:
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
recs = session_records.get(
|
|
418
|
+
# Reconstruct the effective key from the summary's first snippet identity
|
|
419
|
+
esk = _esk_from_summary(summary)
|
|
420
|
+
recs = session_records.get(esk, [])
|
|
368
421
|
fp = snippet_fingerprint(recs)
|
|
369
422
|
|
|
370
423
|
if not force_refresh:
|
|
371
|
-
cached = read_cached_summary(cpath,
|
|
424
|
+
cached = read_cached_summary(cpath, esk)
|
|
372
425
|
if cached and is_fresh(cached, fp):
|
|
373
426
|
results.append(cached)
|
|
374
427
|
continue
|
|
@@ -376,7 +429,7 @@ def get_session_summaries(repo_path, force_refresh=False):
|
|
|
376
429
|
# Generate and cache
|
|
377
430
|
summary["generated_at"] = datetime.now(timezone.utc).isoformat()
|
|
378
431
|
summary["snippet_fingerprint"] = fp
|
|
379
|
-
write_cached_summary(cpath, summary)
|
|
432
|
+
write_cached_summary(cpath, summary, esk)
|
|
380
433
|
results.append(summary)
|
|
381
434
|
wrote_any = True
|
|
382
435
|
|
|
@@ -237,6 +237,98 @@ def bump_policy_version(gator_dir, command_post):
|
|
|
237
237
|
return policy_date, changed
|
|
238
238
|
|
|
239
239
|
|
|
240
|
+
def merge_hooks_into_settings(settings_path, hooks_template_path):
|
|
241
|
+
"""Merge Gator hooks into a vendor settings JSON file without clobbering.
|
|
242
|
+
|
|
243
|
+
If the settings file doesn't exist, writes the hooks template as-is.
|
|
244
|
+
If it exists, deep-merges the hooks block: for each hook event,
|
|
245
|
+
appends Gator hook groups that aren't already present (identified by
|
|
246
|
+
the gator-session-start.py command string).
|
|
247
|
+
|
|
248
|
+
Returns: 'add', 'update', or 'unchanged'.
|
|
249
|
+
"""
|
|
250
|
+
GATOR_HOOK_MARKER = "gator-session-start.py"
|
|
251
|
+
|
|
252
|
+
hooks_template = json.loads(hooks_template_path.read_text(encoding="utf-8"))
|
|
253
|
+
template_hooks = hooks_template.get("hooks", {})
|
|
254
|
+
|
|
255
|
+
if not settings_path.exists():
|
|
256
|
+
settings_path.parent.mkdir(parents=True, exist_ok=True)
|
|
257
|
+
settings_path.write_text(
|
|
258
|
+
json.dumps(hooks_template, indent=2) + "\n", encoding="utf-8"
|
|
259
|
+
)
|
|
260
|
+
return "add"
|
|
261
|
+
|
|
262
|
+
# Read existing settings
|
|
263
|
+
try:
|
|
264
|
+
existing = json.loads(settings_path.read_text(encoding="utf-8"))
|
|
265
|
+
except (json.JSONDecodeError, ValueError):
|
|
266
|
+
# Corrupt file — don't clobber, skip
|
|
267
|
+
return "unchanged"
|
|
268
|
+
|
|
269
|
+
if not isinstance(existing, dict):
|
|
270
|
+
return "unchanged"
|
|
271
|
+
|
|
272
|
+
# Validate hooks shape — bail if it's not what we expect
|
|
273
|
+
existing_hooks = existing.get("hooks", {})
|
|
274
|
+
if not isinstance(existing_hooks, dict):
|
|
275
|
+
# hooks is present but wrong type (e.g., a list) — don't clobber
|
|
276
|
+
return "unchanged"
|
|
277
|
+
|
|
278
|
+
needs_update = False
|
|
279
|
+
|
|
280
|
+
for event_name, groups in template_hooks.items():
|
|
281
|
+
if event_name not in existing_hooks:
|
|
282
|
+
existing_hooks[event_name] = groups
|
|
283
|
+
needs_update = True
|
|
284
|
+
else:
|
|
285
|
+
event_value = existing_hooks[event_name]
|
|
286
|
+
if not isinstance(event_value, list):
|
|
287
|
+
# Event value is wrong shape — skip this event, don't corrupt
|
|
288
|
+
continue
|
|
289
|
+
existing_json = json.dumps(event_value)
|
|
290
|
+
if GATOR_HOOK_MARKER not in existing_json:
|
|
291
|
+
event_value.extend(groups)
|
|
292
|
+
needs_update = True
|
|
293
|
+
|
|
294
|
+
if not needs_update:
|
|
295
|
+
return "unchanged"
|
|
296
|
+
|
|
297
|
+
existing["hooks"] = existing_hooks
|
|
298
|
+
settings_path.write_text(
|
|
299
|
+
json.dumps(existing, indent=2) + "\n", encoding="utf-8"
|
|
300
|
+
)
|
|
301
|
+
return "update"
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
# Vendor hook config: (template_file, target_dir, target_filename)
|
|
305
|
+
VENDOR_HOOK_CONFIGS = [
|
|
306
|
+
("vendor-hooks/claude-settings.json", ".claude", "settings.json"),
|
|
307
|
+
("vendor-hooks/codex-hooks.json", ".codex", "hooks.json"),
|
|
308
|
+
("vendor-hooks/gemini-settings.json", ".gemini", "settings.json"),
|
|
309
|
+
]
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def install_vendor_hooks(templates_dir, repo_root):
|
|
313
|
+
"""Install vendor SessionStart hook configs into the repo.
|
|
314
|
+
|
|
315
|
+
Merge-safe: injects Gator hooks into existing settings files
|
|
316
|
+
without clobbering user content (permissions, env vars, other hooks).
|
|
317
|
+
|
|
318
|
+
Returns count of files added or updated.
|
|
319
|
+
"""
|
|
320
|
+
changed = 0
|
|
321
|
+
for template_file, target_dir, target_name in VENDOR_HOOK_CONFIGS:
|
|
322
|
+
src = templates_dir / template_file
|
|
323
|
+
if not src.exists():
|
|
324
|
+
continue
|
|
325
|
+
dest = repo_root / target_dir / target_name
|
|
326
|
+
result = merge_hooks_into_settings(dest, src)
|
|
327
|
+
if result in ("add", "update"):
|
|
328
|
+
changed += 1
|
|
329
|
+
return changed
|
|
330
|
+
|
|
331
|
+
|
|
240
332
|
def execute_updates(plan):
|
|
241
333
|
"""Execute the update plan — copy files that need adding or updating.
|
|
242
334
|
|
|
@@ -264,18 +356,18 @@ def execute_updates(plan):
|
|
|
264
356
|
def plan_hook_updates(gator_dir, repo_root):
|
|
265
357
|
"""Plan git hook installations. Returns list of (hook_name, action).
|
|
266
358
|
|
|
267
|
-
Compares the generated cross-platform Python wrappers to the current
|
|
268
|
-
managed hook dir contents. This matches what install_git_hooks()
|
|
269
|
-
actually writes, so the plan is accurate on both Unix and Windows.
|
|
270
|
-
"""
|
|
271
|
-
git_hooks = get_managed_hook_dir(repo_root)
|
|
359
|
+
Compares the generated cross-platform Python wrappers to the current
|
|
360
|
+
managed hook dir contents. This matches what install_git_hooks()
|
|
361
|
+
actually writes, so the plan is accurate on both Unix and Windows.
|
|
362
|
+
"""
|
|
363
|
+
git_hooks = get_managed_hook_dir(repo_root)
|
|
272
364
|
gator_script = gator_dir / "scripts" / "gator-pre-commit.py"
|
|
273
365
|
|
|
274
366
|
if not git_hooks.parent.is_dir() or not gator_script.exists():
|
|
275
367
|
return []
|
|
276
368
|
|
|
277
|
-
expected_hooks = build_git_hook_wrappers()
|
|
278
|
-
config_needs_update = hooks_config_needs_update(repo_root)
|
|
369
|
+
expected_hooks = build_git_hook_wrappers()
|
|
370
|
+
config_needs_update = hooks_config_needs_update(repo_root)
|
|
279
371
|
results = []
|
|
280
372
|
for hook_name, expected_content in sorted(expected_hooks.items()):
|
|
281
373
|
dest = git_hooks / hook_name
|
|
@@ -283,70 +375,70 @@ def plan_hook_updates(gator_dir, repo_root):
|
|
|
283
375
|
results.append((hook_name, "add"))
|
|
284
376
|
continue
|
|
285
377
|
actual = dest.read_text(encoding="utf-8", errors="replace")
|
|
286
|
-
if actual != expected_content or config_needs_update:
|
|
287
|
-
results.append((hook_name, "update"))
|
|
288
|
-
else:
|
|
289
|
-
results.append((hook_name, "unchanged"))
|
|
378
|
+
if actual != expected_content or config_needs_update:
|
|
379
|
+
results.append((hook_name, "update"))
|
|
380
|
+
else:
|
|
381
|
+
results.append((hook_name, "unchanged"))
|
|
290
382
|
|
|
291
383
|
return results
|
|
292
384
|
|
|
293
385
|
|
|
294
|
-
def _hook_shebang():
|
|
295
|
-
"""Return the platform-correct shebang for hook wrappers."""
|
|
296
|
-
return "#!C:/Windows/py.exe -3" if os.name == "nt" else "#!/usr/bin/env python3"
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
def get_managed_hooks_path_value():
|
|
300
|
-
"""Return the core.hooksPath value Gator manages on this platform."""
|
|
301
|
-
return ".git/gator-hooks" if os.name == "nt" else None
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
def get_managed_hook_dir(repo_root):
|
|
305
|
-
"""Return the hook directory Gator actively manages."""
|
|
306
|
-
hooks_path = get_managed_hooks_path_value()
|
|
307
|
-
if hooks_path:
|
|
308
|
-
return repo_root / Path(hooks_path)
|
|
309
|
-
return repo_root / ".git" / "hooks"
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
def get_hook_probe_dirs(repo_root):
|
|
313
|
-
"""Return installed-hook directories to probe, managed dir first."""
|
|
314
|
-
probe_dirs = [get_managed_hook_dir(repo_root)]
|
|
315
|
-
legacy = repo_root / ".git" / "hooks"
|
|
316
|
-
if legacy not in probe_dirs:
|
|
317
|
-
probe_dirs.append(legacy)
|
|
318
|
-
return probe_dirs
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
def hooks_config_needs_update(repo_root):
|
|
322
|
-
"""Return True when core.hooksPath is missing or wrong."""
|
|
323
|
-
expected = get_managed_hooks_path_value()
|
|
324
|
-
if not expected:
|
|
325
|
-
return False
|
|
326
|
-
actual, ok = git("config", "--local", "--get", "core.hooksPath", cwd=repo_root)
|
|
327
|
-
return not ok or actual.strip() != expected
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
def _configure_managed_hooks_path(repo_root):
|
|
331
|
-
"""Set core.hooksPath when this platform uses a non-default dir."""
|
|
332
|
-
expected = get_managed_hooks_path_value()
|
|
333
|
-
if not expected:
|
|
334
|
-
return True
|
|
335
|
-
_, ok = git("config", "--local", "core.hooksPath", expected, cwd=repo_root)
|
|
336
|
-
return ok
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
def get_managed_hook_display_path():
|
|
340
|
-
"""Return the human-readable relative hook path for output."""
|
|
341
|
-
return get_managed_hooks_path_value() or ".git/hooks"
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
def build_git_hook_wrappers():
|
|
345
|
-
"""Return the exact hook wrapper contents gator installs.
|
|
346
|
-
|
|
347
|
-
The subprocess.call inside uses the exact interpreter path — spaces are
|
|
348
|
-
safe there because it's a Python string argument, not a shebang.
|
|
349
|
-
"""
|
|
386
|
+
def _hook_shebang():
|
|
387
|
+
"""Return the platform-correct shebang for hook wrappers."""
|
|
388
|
+
return "#!C:/Windows/py.exe -3" if os.name == "nt" else "#!/usr/bin/env python3"
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def get_managed_hooks_path_value():
|
|
392
|
+
"""Return the core.hooksPath value Gator manages on this platform."""
|
|
393
|
+
return ".git/gator-hooks" if os.name == "nt" else None
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def get_managed_hook_dir(repo_root):
|
|
397
|
+
"""Return the hook directory Gator actively manages."""
|
|
398
|
+
hooks_path = get_managed_hooks_path_value()
|
|
399
|
+
if hooks_path:
|
|
400
|
+
return repo_root / Path(hooks_path)
|
|
401
|
+
return repo_root / ".git" / "hooks"
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def get_hook_probe_dirs(repo_root):
|
|
405
|
+
"""Return installed-hook directories to probe, managed dir first."""
|
|
406
|
+
probe_dirs = [get_managed_hook_dir(repo_root)]
|
|
407
|
+
legacy = repo_root / ".git" / "hooks"
|
|
408
|
+
if legacy not in probe_dirs:
|
|
409
|
+
probe_dirs.append(legacy)
|
|
410
|
+
return probe_dirs
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def hooks_config_needs_update(repo_root):
|
|
414
|
+
"""Return True when core.hooksPath is missing or wrong."""
|
|
415
|
+
expected = get_managed_hooks_path_value()
|
|
416
|
+
if not expected:
|
|
417
|
+
return False
|
|
418
|
+
actual, ok = git("config", "--local", "--get", "core.hooksPath", cwd=repo_root)
|
|
419
|
+
return not ok or actual.strip() != expected
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def _configure_managed_hooks_path(repo_root):
|
|
423
|
+
"""Set core.hooksPath when this platform uses a non-default dir."""
|
|
424
|
+
expected = get_managed_hooks_path_value()
|
|
425
|
+
if not expected:
|
|
426
|
+
return True
|
|
427
|
+
_, ok = git("config", "--local", "core.hooksPath", expected, cwd=repo_root)
|
|
428
|
+
return ok
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def get_managed_hook_display_path():
|
|
432
|
+
"""Return the human-readable relative hook path for output."""
|
|
433
|
+
return get_managed_hooks_path_value() or ".git/hooks"
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def build_git_hook_wrappers():
|
|
437
|
+
"""Return the exact hook wrapper contents gator installs.
|
|
438
|
+
|
|
439
|
+
The subprocess.call inside uses the exact interpreter path — spaces are
|
|
440
|
+
safe there because it's a Python string argument, not a shebang.
|
|
441
|
+
"""
|
|
350
442
|
python_path = sys.executable.replace("\\", "/")
|
|
351
443
|
gator_script = ".gator/scripts/gator-pre-commit.py"
|
|
352
444
|
shebang = _hook_shebang()
|
|
@@ -370,19 +462,19 @@ def build_git_hook_wrappers():
|
|
|
370
462
|
}
|
|
371
463
|
|
|
372
464
|
|
|
373
|
-
def install_git_hooks(gator_dir, repo_root):
|
|
374
|
-
"""Install/refresh git hooks as cross-platform Python wrappers.
|
|
375
|
-
|
|
376
|
-
Writes Python-shebang hooks that call gator-pre-commit.py directly.
|
|
377
|
-
Windows installs into `.git/gator-hooks/` and sets `core.hooksPath`
|
|
378
|
-
to bypass the Git-for-Windows/MSYS launcher path. Unix installs into
|
|
379
|
-
the normal `.git/hooks/` directory.
|
|
380
|
-
|
|
381
|
-
@writes: managed git hook dir, git config core.hooksPath (Windows only)
|
|
382
|
-
"""
|
|
383
|
-
git_hooks = get_managed_hook_dir(repo_root)
|
|
384
|
-
if not git_hooks.parent.is_dir():
|
|
385
|
-
return 0
|
|
465
|
+
def install_git_hooks(gator_dir, repo_root):
|
|
466
|
+
"""Install/refresh git hooks as cross-platform Python wrappers.
|
|
467
|
+
|
|
468
|
+
Writes Python-shebang hooks that call gator-pre-commit.py directly.
|
|
469
|
+
Windows installs into `.git/gator-hooks/` and sets `core.hooksPath`
|
|
470
|
+
to bypass the Git-for-Windows/MSYS launcher path. Unix installs into
|
|
471
|
+
the normal `.git/hooks/` directory.
|
|
472
|
+
|
|
473
|
+
@writes: managed git hook dir, git config core.hooksPath (Windows only)
|
|
474
|
+
"""
|
|
475
|
+
git_hooks = get_managed_hook_dir(repo_root)
|
|
476
|
+
if not git_hooks.parent.is_dir():
|
|
477
|
+
return 0
|
|
386
478
|
|
|
387
479
|
git_hooks.mkdir(parents=True, exist_ok=True)
|
|
388
480
|
installed = 0
|
|
@@ -393,12 +485,12 @@ def install_git_hooks(gator_dir, repo_root):
|
|
|
393
485
|
try:
|
|
394
486
|
import stat
|
|
395
487
|
dest.chmod(dest.stat().st_mode | stat.S_IEXEC)
|
|
396
|
-
except (OSError, AttributeError):
|
|
397
|
-
pass
|
|
398
|
-
installed += 1
|
|
399
|
-
|
|
400
|
-
_configure_managed_hooks_path(repo_root)
|
|
401
|
-
return installed
|
|
488
|
+
except (OSError, AttributeError):
|
|
489
|
+
pass
|
|
490
|
+
installed += 1
|
|
491
|
+
|
|
492
|
+
_configure_managed_hooks_path(repo_root)
|
|
493
|
+
return installed
|
|
402
494
|
|
|
403
495
|
|
|
404
496
|
# --- Output ---
|
|
@@ -429,15 +521,15 @@ def print_plan(plan, dry_run=False, hooks=None):
|
|
|
429
521
|
print(f" ~ {dest.name}")
|
|
430
522
|
print()
|
|
431
523
|
|
|
432
|
-
if hook_adds or hook_updates:
|
|
433
|
-
print(f" Git hooks ({len(hook_adds)} new, {len(hook_updates)} updated):")
|
|
434
|
-
hook_dir = get_managed_hook_display_path()
|
|
435
|
-
for name, action in (hooks or []):
|
|
436
|
-
if action == "add":
|
|
437
|
-
print(f" + {hook_dir}/{name}")
|
|
438
|
-
elif action == "update":
|
|
439
|
-
print(f" ~ {hook_dir}/{name}")
|
|
440
|
-
print()
|
|
524
|
+
if hook_adds or hook_updates:
|
|
525
|
+
print(f" Git hooks ({len(hook_adds)} new, {len(hook_updates)} updated):")
|
|
526
|
+
hook_dir = get_managed_hook_display_path()
|
|
527
|
+
for name, action in (hooks or []):
|
|
528
|
+
if action == "add":
|
|
529
|
+
print(f" + {hook_dir}/{name}")
|
|
530
|
+
elif action == "update":
|
|
531
|
+
print(f" ~ {hook_dir}/{name}")
|
|
532
|
+
print()
|
|
441
533
|
|
|
442
534
|
if not adds and not updates and not hook_adds and not hook_updates:
|
|
443
535
|
print(f" Everything is current. No updates needed.")
|
|
@@ -466,14 +558,14 @@ def print_json_plan(plan, command_post, templates_dir, hooks=None):
|
|
|
466
558
|
"filename": dest.name,
|
|
467
559
|
})
|
|
468
560
|
|
|
469
|
-
hook_items = []
|
|
470
|
-
hook_dir = get_managed_hook_display_path()
|
|
471
|
-
for name, action in (hooks or []):
|
|
472
|
-
hook_items.append({
|
|
473
|
-
"hook": name,
|
|
474
|
-
"action": action,
|
|
475
|
-
"destination": f"{hook_dir}/{name}",
|
|
476
|
-
})
|
|
561
|
+
hook_items = []
|
|
562
|
+
hook_dir = get_managed_hook_display_path()
|
|
563
|
+
for name, action in (hooks or []):
|
|
564
|
+
hook_items.append({
|
|
565
|
+
"hook": name,
|
|
566
|
+
"action": action,
|
|
567
|
+
"destination": f"{hook_dir}/{name}",
|
|
568
|
+
})
|
|
477
569
|
|
|
478
570
|
output = {
|
|
479
571
|
"version": VERSION,
|
|
@@ -594,6 +686,11 @@ def main():
|
|
|
594
686
|
if hooks_installed > 0:
|
|
595
687
|
print(f" Git hooks: {hooks_installed} installed/refreshed")
|
|
596
688
|
|
|
689
|
+
# Install/merge vendor SessionStart hook configs
|
|
690
|
+
vendor_hooks_changed = install_vendor_hooks(templates_dir, repo_root)
|
|
691
|
+
if vendor_hooks_changed > 0:
|
|
692
|
+
print(f" Vendor hooks: {vendor_hooks_changed} config(s) installed/updated")
|
|
693
|
+
|
|
597
694
|
# Channel 2: policy sync (only for policy-synced repos, unless --no-policy)
|
|
598
695
|
if args.no_policy:
|
|
599
696
|
pass # Skip channel 2 entirely
|