gator-command 1.0.0__py3-none-any.whl
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/__init__.py +2 -0
- gator_command/cli.py +137 -0
- gator_command/scripts/crawler.py +633 -0
- gator_command/scripts/dashboard/dashboard.css +982 -0
- gator_command/scripts/dashboard/dashboard.html +84 -0
- gator_command/scripts/dashboard/dashboard.js +419 -0
- gator_command/scripts/dashboard/views/audit.js +270 -0
- gator_command/scripts/dashboard/views/fleet.js +307 -0
- gator_command/scripts/dashboard/views/repo.js +599 -0
- gator_command/scripts/dashboard/views/settings.js +173 -0
- gator_command/scripts/dashboard/views/updates.js +308 -0
- gator_command/scripts/enforcer-prompt.md +22 -0
- gator_command/scripts/extract-claude-sessions.py +489 -0
- gator_command/scripts/extract-codex-sessions.py +477 -0
- gator_command/scripts/extract-gemini-sessions.py +410 -0
- gator_command/scripts/gator-audit.py +956 -0
- gator_command/scripts/gator-charter-draft.py +919 -0
- gator_command/scripts/gator-charter-lint.py +427 -0
- gator_command/scripts/gator-charter-verify.py +606 -0
- gator_command/scripts/gator-dashboard.py +1271 -0
- gator_command/scripts/gator-deploy.py +916 -0
- gator_command/scripts/gator-drift.py +569 -0
- gator_command/scripts/gator-enforce.py +82 -0
- gator_command/scripts/gator-fleet-intel.py +460 -0
- gator_command/scripts/gator-fleet-report.py +615 -0
- gator_command/scripts/gator-init-command-post.py +315 -0
- gator_command/scripts/gator-init.py +434 -0
- gator_command/scripts/gator-machine-id.py +153 -0
- gator_command/scripts/gator-policy-status.py +631 -0
- gator_command/scripts/gator-pulse.py +459 -0
- gator_command/scripts/gator-repo-status.py +649 -0
- gator_command/scripts/gator-session-common.py +372 -0
- gator_command/scripts/gator-session-sink.py +831 -0
- gator_command/scripts/gator-sessions.py +1244 -0
- gator_command/scripts/gator-update.py +615 -0
- gator_command/scripts/gator-version.py +38 -0
- gator_command/scripts/gator_core.py +489 -0
- gator_command/scripts/gator_remote.py +381 -0
- gator_command/scripts/gator_runtime.py +142 -0
- gator_command/scripts/gatorize-actions.sh +989 -0
- gator_command/scripts/gatorize-lib.sh +166 -0
- gator_command/scripts/gatorize-post.sh +394 -0
- gator_command/scripts/gatorize.py +1163 -0
- gator_command/scripts/gatorize.sh +185 -0
- gator_command/scripts/generate_markdown.py +212 -0
- gator_command/scripts/generate_wiki.py +424 -0
- gator_command/scripts/graph_health.py +780 -0
- gator_command/scripts/memex-lint.py +286 -0
- gator_command/scripts/memex-lint.sh +205 -0
- gator_command/scripts/memex.py +1472 -0
- gator_command/scripts/memex_formatters.py +191 -0
- gator_command/scripts/memex_state.py +236 -0
- gator_command/scripts/spawn.py +650 -0
- gator_command/templates/gator-starter/blueprints/README.md +32 -0
- gator_command/templates/gator-starter/charterignore +53 -0
- gator_command/templates/gator-starter/charters/README.md +178 -0
- gator_command/templates/gator-starter/charters/_template.md +31 -0
- gator_command/templates/gator-starter/commands/commit.md +33 -0
- gator_command/templates/gator-starter/commands/init.md +11 -0
- gator_command/templates/gator-starter/commands/update.md +5 -0
- gator_command/templates/gator-starter/constitution.md +165 -0
- gator_command/templates/gator-starter/field-guides/README.md +25 -0
- gator_command/templates/gator-starter/gator-start-up.md +119 -0
- gator_command/templates/gator-starter/procedures/charter-alignment.md +83 -0
- gator_command/templates/gator-starter/procedures/enforcer-review.md +317 -0
- gator_command/templates/gator-starter/procedures/field-guide-generation.md +176 -0
- gator_command/templates/gator-starter/procedures/knowledge-capture.md +57 -0
- gator_command/templates/gator-starter/procedures/significance-check.md +69 -0
- gator_command/templates/gator-starter/reference-notes/concierge-responses.md +535 -0
- gator_command/templates/gator-starter/reference-notes/dangerous-patterns.md +91 -0
- gator_command/templates/gator-starter/reference-notes/dashboard-operations.md +22 -0
- gator_command/templates/gator-starter/reference-notes/enforcer-configuration.md +232 -0
- gator_command/templates/gator-starter/reference-notes/example-project.md +289 -0
- gator_command/templates/gator-starter/reference-notes/failure-modes-and-self-correction.md +72 -0
- gator_command/templates/gator-starter/reference-notes/git-workflow.md +60 -0
- gator_command/templates/gator-starter/reference-notes/identity-and-ownership.md +37 -0
- gator_command/templates/gator-starter/reference-notes/refactor-approach.md +155 -0
- gator_command/templates/gator-starter/reference-notes/what-gator-requires-from-a-model.md +108 -0
- gator_command/templates/gator-starter/reference-notes/why-navigation-coding-feels-different.md +99 -0
- gator_command/templates/gator-starter/reference-notes/workflow-profiles.md +155 -0
- gator_command/templates/gator-starter/scripts/__pycache__/enforcer-review.cpython-313.pyc +0 -0
- gator_command/templates/gator-starter/scripts/__pycache__/gator-approve.cpython-313.pyc +0 -0
- gator_command/templates/gator-starter/scripts/__pycache__/gator-init.cpython-313.pyc +0 -0
- gator_command/templates/gator-starter/scripts/__pycache__/gator-pre-commit.cpython-313.pyc +0 -0
- gator_command/templates/gator-starter/scripts/__pycache__/gator-update.cpython-313.pyc +0 -0
- gator_command/templates/gator-starter/scripts/enforcer-prompt.md +55 -0
- gator_command/templates/gator-starter/scripts/enforcer-review.py +1551 -0
- gator_command/templates/gator-starter/scripts/gator-approve.py +139 -0
- gator_command/templates/gator-starter/scripts/gator-enforce.py +82 -0
- gator_command/templates/gator-starter/scripts/gator-init.py +434 -0
- gator_command/templates/gator-starter/scripts/gator-pre-commit.py +2670 -0
- gator_command/templates/gator-starter/scripts/gator-pulse.py +459 -0
- gator_command/templates/gator-starter/scripts/gator-update.py +615 -0
- gator_command/templates/gator-starter/scripts/gator-version.py +38 -0
- gator_command/templates/gator-starter/scripts/gator_core.py +487 -0
- gator_command/templates/gator-starter/scripts/hooks/__pycache__/commit-msgcpython-313.pyc +0 -0
- gator_command/templates/gator-starter/scripts/hooks/__pycache__/post-commitcpython-313.pyc +0 -0
- gator_command/templates/gator-starter/scripts/hooks/__pycache__/pre-commitcpython-313.pyc +0 -0
- gator_command/templates/gator-starter/scripts/hooks/commit-msg +5 -0
- gator_command/templates/gator-starter/scripts/hooks/post-commit +7 -0
- gator_command/templates/gator-starter/scripts/hooks/pre-commit +5 -0
- gator_command/templates/gator-starter/sessions/.gitignore +7 -0
- gator_command/templates/gator-starter/vault/.gitkeep +0 -0
- gator_command/templates/gator-starter/whiteboard.md +5 -0
- gator_command-1.0.0.dist-info/METADATA +122 -0
- gator_command-1.0.0.dist-info/RECORD +110 -0
- gator_command-1.0.0.dist-info/WHEEL +5 -0
- gator_command-1.0.0.dist-info/entry_points.txt +2 -0
- gator_command-1.0.0.dist-info/licenses/LICENSE +21 -0
- gator_command-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
gator-session-common.py — Shared utilities for session archaeology scripts.
|
|
4
|
+
|
|
5
|
+
Contains the machine identity resolver, redaction engine, and
|
|
6
|
+
intelligence extraction logic used by all vendor-specific extraction
|
|
7
|
+
scripts (Claude, Codex, Gemini, Cursor, future).
|
|
8
|
+
|
|
9
|
+
This module is the foundation of the Gator Session Schema — the
|
|
10
|
+
normalized format all vendors map to.
|
|
11
|
+
|
|
12
|
+
Import:
|
|
13
|
+
from gator_session_common import get_machine_identity, redact, extract_intelligence
|
|
14
|
+
|
|
15
|
+
@reads: ~/.gator/machine-id
|
|
16
|
+
@does-not-own: vendor-specific extraction (each script handles its own format)
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import os
|
|
20
|
+
import platform
|
|
21
|
+
import re
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
# Machine Identity
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
GATOR_USER_DIR = Path.home() / ".gator"
|
|
30
|
+
MACHINE_ID_FILE = GATOR_USER_DIR / "machine-id"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_machine_identity():
|
|
34
|
+
"""Get stable machine identity for session summaries.
|
|
35
|
+
|
|
36
|
+
Returns dict with id, hostname, label. Creates machine-id file
|
|
37
|
+
on first call if it doesn't exist.
|
|
38
|
+
|
|
39
|
+
Uses gator-machine-id.py's storage format for compatibility.
|
|
40
|
+
"""
|
|
41
|
+
if MACHINE_ID_FILE.exists():
|
|
42
|
+
data = {}
|
|
43
|
+
try:
|
|
44
|
+
for line in MACHINE_ID_FILE.read_text(encoding="utf-8").splitlines():
|
|
45
|
+
line = line.strip()
|
|
46
|
+
if ":" in line and not line.startswith("#"):
|
|
47
|
+
key, _, value = line.partition(":")
|
|
48
|
+
data[key.strip()] = value.strip()
|
|
49
|
+
except OSError:
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
if "id" in data:
|
|
53
|
+
return {
|
|
54
|
+
"id": data["id"],
|
|
55
|
+
"hostname": data.get("hostname", platform.node()),
|
|
56
|
+
"label": data.get("label", data.get("hostname", platform.node())),
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# No machine-id file — create one
|
|
60
|
+
import uuid
|
|
61
|
+
from datetime import date
|
|
62
|
+
|
|
63
|
+
GATOR_USER_DIR.mkdir(parents=True, exist_ok=True)
|
|
64
|
+
mid = str(uuid.uuid4())
|
|
65
|
+
hostname = platform.node()
|
|
66
|
+
|
|
67
|
+
lines = [
|
|
68
|
+
f"id: {mid}",
|
|
69
|
+
f"hostname: {hostname}",
|
|
70
|
+
f"label: {hostname}",
|
|
71
|
+
f"created: {date.today()}",
|
|
72
|
+
]
|
|
73
|
+
MACHINE_ID_FILE.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
74
|
+
|
|
75
|
+
return {"id": mid, "hostname": hostname, "label": hostname}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# ---------------------------------------------------------------------------
|
|
79
|
+
# Redaction Engine
|
|
80
|
+
# ---------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
# Patterns applied to ALL content (summaries and transcripts)
|
|
83
|
+
REDACT_ALWAYS = [
|
|
84
|
+
# API keys
|
|
85
|
+
(r'sk-[A-Za-z0-9]{20,}', '[REDACTED-API-KEY]'),
|
|
86
|
+
(r'AKIA[A-Z0-9]{16}', '[REDACTED-AWS-KEY]'),
|
|
87
|
+
# Passwords in assignments
|
|
88
|
+
(r'(?i)(password|passwd|pwd)\s*=\s*["\'][^"\']+["\']',
|
|
89
|
+
r'\1 = "[REDACTED]"'),
|
|
90
|
+
# API key/secret assignments
|
|
91
|
+
(r'(?i)(api[_-]?key|secret[_-]?key|access[_-]?token)\s*=\s*["\'][A-Za-z0-9+/=_-]{16,}["\']',
|
|
92
|
+
r'\1 = "[REDACTED]"'),
|
|
93
|
+
# Private key blocks
|
|
94
|
+
(r'-----BEGIN[A-Z ]*PRIVATE KEY-----[\s\S]*?-----END[A-Z ]*PRIVATE KEY-----',
|
|
95
|
+
'[REDACTED-PRIVATE-KEY]'),
|
|
96
|
+
# Bearer tokens
|
|
97
|
+
(r'(?i)Bearer\s+[A-Za-z0-9_.-]{20,}',
|
|
98
|
+
'Bearer [REDACTED-TOKEN]'),
|
|
99
|
+
# Connection strings with embedded passwords
|
|
100
|
+
(r'(?i)(mysql|postgres|mongodb|redis)://[^:]+:[^@]+@',
|
|
101
|
+
r'\1://[REDACTED]@'),
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
# Patterns applied only to SUMMARIES (transcripts keep the original)
|
|
105
|
+
REDACT_SUMMARY = [
|
|
106
|
+
# Full user paths → relative
|
|
107
|
+
(r'[A-Z]:\\Users\\[^\\]+\\', '~/'),
|
|
108
|
+
(r'/home/[^/]+/', '~/'),
|
|
109
|
+
(r'/Users/[^/]+/', '~/'),
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def redact(text, summary_mode=False):
|
|
114
|
+
"""Apply redaction patterns to text.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
text: Content to redact
|
|
118
|
+
summary_mode: If True, also apply summary-only redactions
|
|
119
|
+
(path anonymization)
|
|
120
|
+
|
|
121
|
+
Always redacts: API keys, passwords, private keys, tokens,
|
|
122
|
+
connection strings.
|
|
123
|
+
|
|
124
|
+
Summary-only redacts: full user paths → ~/
|
|
125
|
+
"""
|
|
126
|
+
if not text:
|
|
127
|
+
return text
|
|
128
|
+
|
|
129
|
+
# Always redact secrets
|
|
130
|
+
for pattern, replacement in REDACT_ALWAYS:
|
|
131
|
+
text = re.sub(pattern, replacement, text)
|
|
132
|
+
|
|
133
|
+
# Summary-only redactions
|
|
134
|
+
if summary_mode:
|
|
135
|
+
for pattern, replacement in REDACT_SUMMARY:
|
|
136
|
+
text = re.sub(pattern, replacement, text)
|
|
137
|
+
|
|
138
|
+
return text
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# ---------------------------------------------------------------------------
|
|
142
|
+
# Intelligence Extraction (shared across vendors)
|
|
143
|
+
# ---------------------------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
# Decision-language signals in Architect messages
|
|
146
|
+
DECISION_SIGNALS = [
|
|
147
|
+
"let's ", "let us ", "we should ", "go with ",
|
|
148
|
+
"approved", "yes, ", "do it", "proceed",
|
|
149
|
+
"i want ", "i think ", "i'd like ",
|
|
150
|
+
"decision:", "decided:",
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def extract_intelligence(turns):
|
|
155
|
+
"""Extract decisions, files changed, charters updated, and session goal.
|
|
156
|
+
|
|
157
|
+
Works with the normalized turn format (role, content, tool_calls).
|
|
158
|
+
Vendor-agnostic — all extraction scripts produce turns in this shape.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
turns: list of dicts with role, content, tool_calls, timestamp
|
|
162
|
+
|
|
163
|
+
Returns dict with goal, decisions, files_changed, charters_updated.
|
|
164
|
+
"""
|
|
165
|
+
files_changed = set()
|
|
166
|
+
charters_updated = set()
|
|
167
|
+
decisions = []
|
|
168
|
+
goal = ""
|
|
169
|
+
|
|
170
|
+
for t in turns:
|
|
171
|
+
raw_content = t.get("content", "")
|
|
172
|
+
content = raw_content.strip() if isinstance(raw_content, str) else ""
|
|
173
|
+
role = t.get("role", "")
|
|
174
|
+
|
|
175
|
+
# Goal = first substantive Architect message
|
|
176
|
+
if role == "user" and content and not goal and len(content) > 10:
|
|
177
|
+
goal = redact(content[:200], summary_mode=True)
|
|
178
|
+
|
|
179
|
+
# Files from content (backtick-quoted or double-quoted paths)
|
|
180
|
+
if content:
|
|
181
|
+
for match in re.finditer(r'[`"]([a-zA-Z0-9_./-]+\.[a-zA-Z]{1,10})[`"]', content):
|
|
182
|
+
filepath = match.group(1)
|
|
183
|
+
if ".gator/charters/" in filepath:
|
|
184
|
+
charters_updated.add(filepath.split("/")[-1])
|
|
185
|
+
elif not filepath.startswith("http"):
|
|
186
|
+
files_changed.add(filepath)
|
|
187
|
+
|
|
188
|
+
# Files from tool calls
|
|
189
|
+
for tc in t.get("tool_calls", []):
|
|
190
|
+
tool = tc.get("tool", "")
|
|
191
|
+
if tool:
|
|
192
|
+
files_changed.add(f"[{tool} operation]")
|
|
193
|
+
|
|
194
|
+
# Decisions from Architect messages
|
|
195
|
+
if role == "user" and content:
|
|
196
|
+
lower = content.lower()
|
|
197
|
+
if any(sig in lower for sig in DECISION_SIGNALS):
|
|
198
|
+
decisions.append({
|
|
199
|
+
"timestamp": t.get("timestamp", ""),
|
|
200
|
+
"text": redact(content[:120], summary_mode=True),
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
"goal": goal,
|
|
205
|
+
"files_changed": sorted(files_changed)[:20],
|
|
206
|
+
"charters_updated": sorted(charters_updated),
|
|
207
|
+
"decisions": decisions[:10],
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
# ---------------------------------------------------------------------------
|
|
212
|
+
# Session Identity (composite key, no collisions)
|
|
213
|
+
# ---------------------------------------------------------------------------
|
|
214
|
+
|
|
215
|
+
def make_row_key(metadata):
|
|
216
|
+
"""Generate the canonical unique row key for a session.
|
|
217
|
+
|
|
218
|
+
Hashes session_id + source_path to handle the real case where
|
|
219
|
+
two different files share the same internal session ID (Gemini).
|
|
220
|
+
This key is used everywhere: transcript paths, spool filenames,
|
|
221
|
+
export state, manifest rows.
|
|
222
|
+
"""
|
|
223
|
+
import hashlib
|
|
224
|
+
session_id = metadata.get("session_id", "unknown")
|
|
225
|
+
source_path = metadata.get("source_path", "")
|
|
226
|
+
key = f"{session_id}|{source_path}"
|
|
227
|
+
return hashlib.sha256(key.encode()).hexdigest()[:16]
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def make_transcript_path(metadata):
|
|
231
|
+
"""Generate a collision-free transcript path.
|
|
232
|
+
|
|
233
|
+
Uses the row_key (hash of session_id + source_path) to guarantee
|
|
234
|
+
uniqueness even for Gemini's genuine duplicate session IDs.
|
|
235
|
+
Format: <date>-<repo>-<vendor>-<row_key>.md
|
|
236
|
+
"""
|
|
237
|
+
date = metadata.get("start", "")[:10]
|
|
238
|
+
repo = metadata.get("repo", "unknown")
|
|
239
|
+
vendor = metadata.get("vendor", "unknown")
|
|
240
|
+
rk = make_row_key(metadata)
|
|
241
|
+
transcript_dir = GATOR_USER_DIR / "session-transcripts"
|
|
242
|
+
return transcript_dir / f"{date}-{repo}-{vendor}-{rk}.md"
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
# ---------------------------------------------------------------------------
|
|
246
|
+
# Summary Formatting (shared across vendors — canonical implementation)
|
|
247
|
+
# ---------------------------------------------------------------------------
|
|
248
|
+
|
|
249
|
+
def format_summary_frontmatter(metadata, machine):
|
|
250
|
+
"""Generate standardized YAML frontmatter for session summaries.
|
|
251
|
+
|
|
252
|
+
This is the ONLY implementation. Vendor extractors must call this,
|
|
253
|
+
not hand-roll their own frontmatter.
|
|
254
|
+
"""
|
|
255
|
+
transcript_path = make_transcript_path(metadata)
|
|
256
|
+
|
|
257
|
+
return [
|
|
258
|
+
f"schema: gator-session-summary-v1",
|
|
259
|
+
f"session-id: {metadata.get('session_id', '')}",
|
|
260
|
+
f"date: {metadata.get('start', '')[:10]}",
|
|
261
|
+
f"start: {metadata.get('start', '')}",
|
|
262
|
+
f"end: {metadata.get('end', '')}",
|
|
263
|
+
f"repo: {metadata.get('repo', 'unknown')}",
|
|
264
|
+
f"architect: {metadata.get('architect', metadata.get('pi', 'unknown'))}",
|
|
265
|
+
f"agent: {metadata.get('agent', 'unknown')}",
|
|
266
|
+
f"vendor: {metadata.get('vendor', 'unknown')}",
|
|
267
|
+
f"machine-id: {machine.get('id', '')}",
|
|
268
|
+
f"machine-label: {machine.get('label', '')}",
|
|
269
|
+
f"transcript: {transcript_path}",
|
|
270
|
+
f"turns: {metadata.get('user_turns', 0)} user, {metadata.get('assistant_turns', 0)} assistant",
|
|
271
|
+
f"tools: {', '.join(metadata.get('tools_used', []))}",
|
|
272
|
+
f"branch: {metadata.get('branch', 'unknown')}",
|
|
273
|
+
]
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def format_summary_markdown(turns, metadata):
|
|
277
|
+
"""Generate complete summary markdown (frontmatter + body).
|
|
278
|
+
|
|
279
|
+
This is the canonical summary formatter. All vendor extractors
|
|
280
|
+
should call this instead of implementing their own.
|
|
281
|
+
"""
|
|
282
|
+
machine = get_machine_identity()
|
|
283
|
+
intelligence = extract_intelligence(turns)
|
|
284
|
+
transcript_path = make_transcript_path(metadata)
|
|
285
|
+
|
|
286
|
+
lines = []
|
|
287
|
+
|
|
288
|
+
# Frontmatter
|
|
289
|
+
lines.append("---")
|
|
290
|
+
lines.extend(format_summary_frontmatter(metadata, machine))
|
|
291
|
+
lines.append("---")
|
|
292
|
+
lines.append("")
|
|
293
|
+
|
|
294
|
+
# Body
|
|
295
|
+
lines.append(f"# Session Summary — {metadata.get('start', '')[:10]} {metadata.get('repo', 'unknown')} ({metadata.get('agent', metadata.get('vendor', ''))})")
|
|
296
|
+
lines.append("")
|
|
297
|
+
|
|
298
|
+
lines.append("## Goal")
|
|
299
|
+
lines.append("")
|
|
300
|
+
lines.append(intelligence["goal"] or "*No goal extracted*")
|
|
301
|
+
lines.append("")
|
|
302
|
+
|
|
303
|
+
lines.append("## Decisions")
|
|
304
|
+
lines.append("")
|
|
305
|
+
if intelligence["decisions"]:
|
|
306
|
+
for d in intelligence["decisions"]:
|
|
307
|
+
ts = d["timestamp"][:19] if d["timestamp"] else "?"
|
|
308
|
+
lines.append(f"- [{ts}] {d['text']}")
|
|
309
|
+
else:
|
|
310
|
+
lines.append("*No decisions extracted*")
|
|
311
|
+
lines.append("")
|
|
312
|
+
|
|
313
|
+
lines.append("## Files Changed")
|
|
314
|
+
lines.append("")
|
|
315
|
+
if intelligence["files_changed"]:
|
|
316
|
+
for f in intelligence["files_changed"]:
|
|
317
|
+
lines.append(f"- {f}")
|
|
318
|
+
else:
|
|
319
|
+
lines.append("*No file changes extracted*")
|
|
320
|
+
lines.append("")
|
|
321
|
+
|
|
322
|
+
if intelligence["charters_updated"]:
|
|
323
|
+
lines.append("## Charters Updated")
|
|
324
|
+
lines.append("")
|
|
325
|
+
for c in intelligence["charters_updated"]:
|
|
326
|
+
lines.append(f"- {c}")
|
|
327
|
+
lines.append("")
|
|
328
|
+
|
|
329
|
+
lines.append("## Evidence Location")
|
|
330
|
+
lines.append("")
|
|
331
|
+
lines.append(f"- **Machine**: {machine.get('label', '?')} (`{machine.get('id', '?')}`)")
|
|
332
|
+
lines.append(f"- **Transcript**: `{transcript_path}`")
|
|
333
|
+
lines.append(f"- **Raw source**: vendor-specific local storage")
|
|
334
|
+
lines.append("")
|
|
335
|
+
|
|
336
|
+
return "\n".join(lines)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def format_session_summary_dict(turns, metadata):
|
|
340
|
+
"""Generate structured summary dict for JSON output.
|
|
341
|
+
|
|
342
|
+
Canonical implementation — all vendors use this.
|
|
343
|
+
"""
|
|
344
|
+
machine = get_machine_identity()
|
|
345
|
+
intelligence = extract_intelligence(turns)
|
|
346
|
+
transcript_path = make_transcript_path(metadata)
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
"schema": "gator-session-summary-v1",
|
|
350
|
+
"session_id": metadata.get("session_id", ""),
|
|
351
|
+
"row_key": make_row_key(metadata),
|
|
352
|
+
"date": metadata.get("start", "")[:10],
|
|
353
|
+
"start": metadata.get("start", ""),
|
|
354
|
+
"end": metadata.get("end", ""),
|
|
355
|
+
"repo": metadata.get("repo", "unknown"),
|
|
356
|
+
"architect": metadata.get("architect", metadata.get("pi", "unknown")),
|
|
357
|
+
"agent": metadata.get("agent", "unknown"),
|
|
358
|
+
"vendor": metadata.get("vendor", "unknown"),
|
|
359
|
+
"machine_id": machine.get("id", ""),
|
|
360
|
+
"machine_label": machine.get("label", ""),
|
|
361
|
+
"transcript": str(transcript_path),
|
|
362
|
+
"turns": metadata.get("total_turns", 0),
|
|
363
|
+
"user_turns": metadata.get("user_turns", 0),
|
|
364
|
+
"assistant_turns": metadata.get("assistant_turns", 0),
|
|
365
|
+
"tools": metadata.get("tools_used", []),
|
|
366
|
+
"branches": metadata.get("branches", [metadata.get("branch", "")]),
|
|
367
|
+
"model": metadata.get("model", ""),
|
|
368
|
+
"goal": intelligence["goal"],
|
|
369
|
+
"decisions": intelligence["decisions"],
|
|
370
|
+
"files_changed": intelligence["files_changed"],
|
|
371
|
+
"charters_updated": intelligence["charters_updated"],
|
|
372
|
+
}
|