gator-command 1.2.2__tar.gz → 1.4.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.
Files changed (144) hide show
  1. {gator_command-1.2.2/src/gator_command.egg-info → gator_command-1.4.0}/PKG-INFO +1 -1
  2. {gator_command-1.2.2 → gator_command-1.4.0}/pyproject.toml +1 -1
  3. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/cli.py +8 -7
  4. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/gator-deploy.py +4 -0
  5. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/gator-session-aggregator.py +79 -26
  6. gator_command-1.4.0/src/gator_command/scripts/gator-session-block.py +680 -0
  7. {gator_command-1.2.2/src/gator_command/templates/gator-starter → gator_command-1.4.0/src/gator_command}/scripts/gator-update.py +213 -103
  8. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/gatorize.py +105 -20
  9. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/scripts/gator-pre-commit.py +57 -7
  10. gator_command-1.4.0/src/gator_command/templates/gator-starter/scripts/gator-session-start.py +273 -0
  11. {gator_command-1.2.2/src/gator_command → gator_command-1.4.0/src/gator_command/templates/gator-starter}/scripts/gator-update.py +116 -103
  12. gator_command-1.4.0/src/gator_command/templates/gator-starter/vendor-hooks/claude-settings.json +15 -0
  13. gator_command-1.4.0/src/gator_command/templates/gator-starter/vendor-hooks/codex-hooks.json +15 -0
  14. gator_command-1.4.0/src/gator_command/templates/gator-starter/vendor-hooks/gemini-settings.json +15 -0
  15. {gator_command-1.2.2 → gator_command-1.4.0/src/gator_command.egg-info}/PKG-INFO +1 -1
  16. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command.egg-info/SOURCES.txt +9 -1
  17. {gator_command-1.2.2 → gator_command-1.4.0}/tests/test_pre_commit.py +135 -0
  18. {gator_command-1.2.2 → gator_command-1.4.0}/tests/test_session_aggregator.py +105 -0
  19. gator_command-1.4.0/tests/test_session_block.py +605 -0
  20. gator_command-1.4.0/tests/test_session_start.py +360 -0
  21. gator_command-1.4.0/tests/test_vendor_hooks.py +292 -0
  22. {gator_command-1.2.2 → gator_command-1.4.0}/LICENSE +0 -0
  23. {gator_command-1.2.2 → gator_command-1.4.0}/PYPI_README.md +0 -0
  24. {gator_command-1.2.2 → gator_command-1.4.0}/README.md +0 -0
  25. {gator_command-1.2.2 → gator_command-1.4.0}/setup.cfg +0 -0
  26. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/__init__.py +0 -0
  27. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/crawler.py +0 -0
  28. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/dashboard/dashboard.css +0 -0
  29. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/dashboard/dashboard.html +0 -0
  30. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/dashboard/dashboard.js +0 -0
  31. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/dashboard/views/audit.js +0 -0
  32. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/dashboard/views/fleet.js +0 -0
  33. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/dashboard/views/repo.js +0 -0
  34. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/dashboard/views/settings.js +0 -0
  35. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/dashboard/views/updates.js +0 -0
  36. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/enforcer-prompt.md +0 -0
  37. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/extract-claude-sessions.py +0 -0
  38. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/extract-codex-sessions.py +0 -0
  39. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/extract-gemini-sessions.py +0 -0
  40. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/gator-audit-renderers.py +0 -0
  41. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/gator-audit.py +0 -0
  42. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/gator-charter-draft.py +0 -0
  43. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/gator-charter-lint.py +0 -0
  44. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/gator-charter-verify.py +0 -0
  45. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/gator-dashboard.py +0 -0
  46. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/gator-drift.py +0 -0
  47. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/gator-enforce.py +0 -0
  48. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/gator-fleet-intel.py +0 -0
  49. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/gator-fleet-report.py +0 -0
  50. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/gator-init-command-post.py +0 -0
  51. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/gator-init.py +0 -0
  52. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/gator-machine-id.py +0 -0
  53. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/gator-policy-status.py +0 -0
  54. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/gator-pulse.py +0 -0
  55. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/gator-repo-status.py +0 -0
  56. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/gator-session-common.py +0 -0
  57. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/gator-session-sink.py +0 -0
  58. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/gator-sessions.py +0 -0
  59. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/gator-version.py +0 -0
  60. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/gator_core.py +0 -0
  61. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/gator_remote.py +0 -0
  62. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/gator_runtime.py +0 -0
  63. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/gatorize-actions.sh +0 -0
  64. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/gatorize-lib.sh +0 -0
  65. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/gatorize-post.sh +0 -0
  66. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/gatorize.sh +0 -0
  67. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/generate_markdown.py +0 -0
  68. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/generate_wiki.py +0 -0
  69. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/graph_health.py +0 -0
  70. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/memex-lint.py +0 -0
  71. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/memex-lint.sh +0 -0
  72. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/memex.py +0 -0
  73. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/memex_formatters.py +0 -0
  74. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/memex_state.py +0 -0
  75. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/scripts/spawn.py +0 -0
  76. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/blueprints/README.md +0 -0
  77. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/charterignore +0 -0
  78. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/charters/README.md +0 -0
  79. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/charters/_template.md +0 -0
  80. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/commands/commit.md +0 -0
  81. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/commands/init.md +0 -0
  82. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/commands/update.md +0 -0
  83. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/constitution.md +0 -0
  84. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/field-guides/README.md +0 -0
  85. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/gator-start-up.md +0 -0
  86. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/procedures/charter-alignment.md +0 -0
  87. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/procedures/enforcer-review.md +0 -0
  88. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/procedures/field-guide-generation.md +0 -0
  89. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/procedures/knowledge-capture.md +0 -0
  90. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/procedures/significance-check.md +0 -0
  91. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/reference-notes/concierge-responses.md +0 -0
  92. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/reference-notes/dangerous-patterns.md +0 -0
  93. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/reference-notes/dashboard-operations.md +0 -0
  94. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/reference-notes/enforcer-configuration.md +0 -0
  95. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/reference-notes/example-project.md +0 -0
  96. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/reference-notes/failure-modes-and-self-correction.md +0 -0
  97. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/reference-notes/git-workflow.md +0 -0
  98. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/reference-notes/identity-and-ownership.md +0 -0
  99. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/reference-notes/refactor-approach.md +0 -0
  100. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/reference-notes/what-gator-requires-from-a-model.md +0 -0
  101. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/reference-notes/why-navigation-coding-feels-different.md +0 -0
  102. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/reference-notes/workflow-profiles.md +0 -0
  103. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/scripts/__pycache__/enforcer-review.cpython-313.pyc +0 -0
  104. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/scripts/__pycache__/gator-approve.cpython-313.pyc +0 -0
  105. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/scripts/__pycache__/gator-init.cpython-313.pyc +0 -0
  106. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/scripts/__pycache__/gator-pre-commit.cpython-313.pyc +0 -0
  107. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/scripts/__pycache__/gator-update.cpython-313.pyc +0 -0
  108. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/scripts/enforcer-prompt.md +0 -0
  109. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/scripts/enforcer-review.py +0 -0
  110. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/scripts/gator-approve.py +0 -0
  111. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/scripts/gator-enforce.py +0 -0
  112. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/scripts/gator-init.py +0 -0
  113. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/scripts/gator-pulse.py +0 -0
  114. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/scripts/gator-version.py +0 -0
  115. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/scripts/gator_core.py +0 -0
  116. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/scripts/hooks/__pycache__/commit-msgcpython-313.pyc +0 -0
  117. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/scripts/hooks/__pycache__/post-commitcpython-313.pyc +0 -0
  118. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/scripts/hooks/__pycache__/pre-commitcpython-313.pyc +0 -0
  119. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/scripts/hooks/commit-msg +0 -0
  120. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/scripts/hooks/post-commit +0 -0
  121. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/scripts/hooks/pre-commit +0 -0
  122. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/sessions/.gitignore +0 -0
  123. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/vault/.gitkeep +0 -0
  124. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command/templates/gator-starter/whiteboard.md +0 -0
  125. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command.egg-info/dependency_links.txt +0 -0
  126. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command.egg-info/entry_points.txt +0 -0
  127. {gator_command-1.2.2 → gator_command-1.4.0}/src/gator_command.egg-info/top_level.txt +0 -0
  128. {gator_command-1.2.2 → gator_command-1.4.0}/tests/test_audit.py +0 -0
  129. {gator_command-1.2.2 → gator_command-1.4.0}/tests/test_audit_integration.py +0 -0
  130. {gator_command-1.2.2 → gator_command-1.4.0}/tests/test_charter_lint.py +0 -0
  131. {gator_command-1.2.2 → gator_command-1.4.0}/tests/test_dashboard_sessions.py +0 -0
  132. {gator_command-1.2.2 → gator_command-1.4.0}/tests/test_deploy.py +0 -0
  133. {gator_command-1.2.2 → gator_command-1.4.0}/tests/test_drift.py +0 -0
  134. {gator_command-1.2.2 → gator_command-1.4.0}/tests/test_fleet_intel.py +0 -0
  135. {gator_command-1.2.2 → gator_command-1.4.0}/tests/test_fleet_report.py +0 -0
  136. {gator_command-1.2.2 → gator_command-1.4.0}/tests/test_gator_core.py +0 -0
  137. {gator_command-1.2.2 → gator_command-1.4.0}/tests/test_gatorize.py +0 -0
  138. {gator_command-1.2.2 → gator_command-1.4.0}/tests/test_hooks.py +0 -0
  139. {gator_command-1.2.2 → gator_command-1.4.0}/tests/test_init.py +0 -0
  140. {gator_command-1.2.2 → gator_command-1.4.0}/tests/test_memex_state.py +0 -0
  141. {gator_command-1.2.2 → gator_command-1.4.0}/tests/test_packaging.py +0 -0
  142. {gator_command-1.2.2 → gator_command-1.4.0}/tests/test_remote.py +0 -0
  143. {gator_command-1.2.2 → gator_command-1.4.0}/tests/test_session_sink.py +0 -0
  144. {gator_command-1.2.2 → gator_command-1.4.0}/tests/test_sessions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gator-command
3
- Version: 1.2.2
3
+ Version: 1.4.0
4
4
  Summary: Git-native governance for AI-assisted engineering
5
5
  Author-email: Cumberland Laboratories <support@cumberlandlaboratories.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gator-command"
7
- version = "1.2.2"
7
+ version = "1.4.0"
8
8
  description = "Git-native governance for AI-assisted engineering"
9
9
  readme = "PYPI_README.md"
10
10
  license = {text = "MIT"}
@@ -75,6 +75,7 @@ COMMANDS = {
75
75
  "audit": ("gator-audit.py", "Run governance audit"),
76
76
  "drift": ("gator-drift.py", "Check fleet for governance drift"),
77
77
  "fleet": ("gator-fleet-report.py", "Fleet status report"),
78
+ "session-blocks": ("gator-session-block.py", "Generate session-block companions"),
78
79
  }
79
80
 
80
81
 
@@ -121,13 +122,13 @@ def main():
121
122
  _run_script(scripts_dir, script_name, remaining)
122
123
 
123
124
 
124
- def _get_version():
125
- """Get version from the package's single-source version resolver."""
126
- try:
127
- from gator_command import __version__
128
- return __version__
129
- except ImportError:
130
- return "unknown"
125
+ def _get_version():
126
+ """Get version from the package's single-source version resolver."""
127
+ try:
128
+ from gator_command import __version__
129
+ return __version__
130
+ except ImportError:
131
+ return "unknown"
131
132
 
132
133
 
133
134
  if __name__ == "__main__":
@@ -158,8 +158,12 @@ ROOT_GITIGNORE = (
158
158
  "# Session snippets — contain machine-id, not for public repos\n"
159
159
  ".gator/session-snippets/\n"
160
160
  "\n"
161
+ "# Session blocks — local-only transcript evidence\n"
162
+ ".gator/session-blocks/\n"
163
+ "\n"
161
164
  "# Machine-local state (not portable)\n"
162
165
  ".gator/product-source.json\n"
166
+ ".gator/active-vendor-session.json\n"
163
167
  )
164
168
 
165
169
 
@@ -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>/<session_id>.json (cached summaries)
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 (repo, session_id) and aggregate into summaries.
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 (repo, session_id)
112
+ # Group by effective_session_key
87
113
  groups = {}
88
114
  for s in snippets:
89
- key = (s.get("repo", ""), s.get("session_id", ""))
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 (repo, session_id), group in groups.items():
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
- "repo": repo,
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 _summary_cache_filename(repo, session_id):
277
- """Build a cache filename from the (repo, session_id) compound key.
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
- Uses a short hash prefix of the repo name to avoid filename collisions
280
- when two repos share a session_id in the same cache directory.
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
- if repo:
283
- repo_prefix = hashlib.sha256(repo.encode()).hexdigest()[:6]
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, repo, session_id):
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(repo, session_id)
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(summary.get("repo", ""), summary["session_id"])
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: (repo, session_id) -> list of records (for fingerprinting)
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"].get("repo", ""), r["data"].get("session_id", ""))
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
- repo = summary["repo"]
366
- sid = summary["session_id"]
367
- recs = session_records.get((repo, sid), [])
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, repo, sid)
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