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,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* audit.js — Audit view for Gator Dashboard.
|
|
3
|
+
*
|
|
4
|
+
* Data source: data.audit (gator-audit --json)
|
|
5
|
+
*
|
|
6
|
+
* Primary question: can I show human oversight? where are the gaps?
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
(function () {
|
|
10
|
+
"use strict";
|
|
11
|
+
|
|
12
|
+
window.GatorViews = window.GatorViews || {};
|
|
13
|
+
|
|
14
|
+
function escHtml(str) {
|
|
15
|
+
return String(str)
|
|
16
|
+
.replace(/&/g, "&")
|
|
17
|
+
.replace(/</g, "<")
|
|
18
|
+
.replace(/>/g, ">")
|
|
19
|
+
.replace(/"/g, """);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function fmtTs(ts) {
|
|
23
|
+
if (!ts) return "—";
|
|
24
|
+
try {
|
|
25
|
+
return new Date(ts).toLocaleDateString(undefined, {
|
|
26
|
+
year: "numeric", month: "short", day: "numeric",
|
|
27
|
+
});
|
|
28
|
+
} catch (_) { return ts; }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ── significance bar chart ─────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
function renderSigBars(dist) {
|
|
34
|
+
const order = ["critical", "high", "medium", "low", "routine"];
|
|
35
|
+
const colors = {
|
|
36
|
+
critical: "#a02020",
|
|
37
|
+
high: "#c44020",
|
|
38
|
+
medium: "#b8860b",
|
|
39
|
+
low: "#4a7a4a",
|
|
40
|
+
routine: "#888",
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const total = Object.values(dist).reduce((s, v) => s + v, 0);
|
|
44
|
+
if (total === 0) return "<p class='muted'>No governed commits found in this period.</p>";
|
|
45
|
+
|
|
46
|
+
let html = "";
|
|
47
|
+
for (const level of order) {
|
|
48
|
+
const count = dist[level] || 0;
|
|
49
|
+
if (count === 0) continue;
|
|
50
|
+
const pct = Math.round((count / total) * 100);
|
|
51
|
+
html += `
|
|
52
|
+
<div class="sig-bar">
|
|
53
|
+
<span class="sig-bar-label">${escHtml(level)}</span>
|
|
54
|
+
<div class="sig-bar-track">
|
|
55
|
+
<div class="sig-bar-fill" style="width:${pct}%;background:${colors[level] || '#888'}"></div>
|
|
56
|
+
</div>
|
|
57
|
+
<span class="sig-bar-count">${count}</span>
|
|
58
|
+
</div>
|
|
59
|
+
`;
|
|
60
|
+
}
|
|
61
|
+
return html;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ── governed commits table ─────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
function renderGoverned(govCommits) {
|
|
67
|
+
const entries = Object.entries(govCommits).filter(([k]) => k !== "total");
|
|
68
|
+
if (entries.length === 0) return "<p class='muted'>No data.</p>";
|
|
69
|
+
|
|
70
|
+
entries.sort((a, b) => b[1] - a[1]);
|
|
71
|
+
|
|
72
|
+
let html = `
|
|
73
|
+
<table class="data-table">
|
|
74
|
+
<thead><tr><th>Repo</th><th>Governed commits (7d)</th></tr></thead>
|
|
75
|
+
<tbody>
|
|
76
|
+
`;
|
|
77
|
+
for (const [repo, count] of entries) {
|
|
78
|
+
html += `<tr><td>${escHtml(repo)}</td><td class="mono">${count}</td></tr>`;
|
|
79
|
+
}
|
|
80
|
+
html += "</tbody></table>";
|
|
81
|
+
return html;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ── override events table ──────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
function renderOverrides(events) {
|
|
87
|
+
if (!events || events.length === 0) {
|
|
88
|
+
return "<p class='muted'>No override events found.</p>";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let html = `
|
|
92
|
+
<table class="data-table">
|
|
93
|
+
<thead>
|
|
94
|
+
<tr>
|
|
95
|
+
<th>Repo</th>
|
|
96
|
+
<th>Commit</th>
|
|
97
|
+
<th>Date</th>
|
|
98
|
+
<th>Type</th>
|
|
99
|
+
<th>Approver</th>
|
|
100
|
+
</tr>
|
|
101
|
+
</thead>
|
|
102
|
+
<tbody>
|
|
103
|
+
`;
|
|
104
|
+
for (const ev of events) {
|
|
105
|
+
html += `
|
|
106
|
+
<tr>
|
|
107
|
+
<td>${escHtml(ev.repo || "—")}</td>
|
|
108
|
+
<td class="mono">${escHtml((ev.hash || "").slice(0, 7))}</td>
|
|
109
|
+
<td class="nowrap">${fmtTs(ev.timestamp)}</td>
|
|
110
|
+
<td>${escHtml(ev.override_type || "—")}</td>
|
|
111
|
+
<td>${escHtml(ev.approver || "—")}</td>
|
|
112
|
+
</tr>
|
|
113
|
+
`;
|
|
114
|
+
}
|
|
115
|
+
html += "</tbody></table>";
|
|
116
|
+
return html;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ── render ─────────────────────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
window.GatorViews.audit = function (data, container) {
|
|
122
|
+
const audit = data.audit || {};
|
|
123
|
+
|
|
124
|
+
if (audit.error) {
|
|
125
|
+
container.innerHTML = `<div class="error-block">Audit data unavailable: ${escHtml(audit.error)}</div>`;
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const governance = audit.governance || {};
|
|
130
|
+
const sessions = audit.sessions || {};
|
|
131
|
+
const overrides = audit.override_events || [];
|
|
132
|
+
const sigDist = audit.significance_distribution || {};
|
|
133
|
+
const govCommits = audit.governed_commits || {};
|
|
134
|
+
const sinceDays = audit.since_days || 7;
|
|
135
|
+
|
|
136
|
+
// Compute summary metrics
|
|
137
|
+
const totalRepos = governance.repos || 0;
|
|
138
|
+
const hookCoverage = totalRepos > 0
|
|
139
|
+
? Math.round((governance.hooks_installed / totalRepos) * 100)
|
|
140
|
+
: 0;
|
|
141
|
+
const overrideRate = (govCommits.total || 0) > 0
|
|
142
|
+
? Math.round((overrides.length / govCommits.total) * 100)
|
|
143
|
+
: 0;
|
|
144
|
+
|
|
145
|
+
// Session summary coverage: repos with recent sessions / total repos
|
|
146
|
+
const reposWithSessions = Object.keys(sessions.by_repo || {}).length;
|
|
147
|
+
const sessionCoverage = totalRepos > 0
|
|
148
|
+
? Math.round((reposWithSessions / totalRepos) * 100)
|
|
149
|
+
: 0;
|
|
150
|
+
|
|
151
|
+
let html = `
|
|
152
|
+
<div class="view-header">
|
|
153
|
+
<span class="view-title">Audit</span>
|
|
154
|
+
<span class="view-subtitle">Last ${sinceDays} days · ${totalRepos} repos</span>
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
<div class="card-row">
|
|
158
|
+
<div class="card">
|
|
159
|
+
<div class="card-label">Hook coverage</div>
|
|
160
|
+
<div class="card-value">${hookCoverage}%</div>
|
|
161
|
+
<div class="card-note">${governance.hooks_installed || 0} of ${totalRepos} repos</div>
|
|
162
|
+
</div>
|
|
163
|
+
<div class="card">
|
|
164
|
+
<div class="card-label">Governed commits</div>
|
|
165
|
+
<div class="card-value">${govCommits.total || 0}</div>
|
|
166
|
+
<div class="card-note">fleet total, ${sinceDays}d window</div>
|
|
167
|
+
</div>
|
|
168
|
+
<div class="card">
|
|
169
|
+
<div class="card-label">Override events</div>
|
|
170
|
+
<div class="card-value" style="color:${overrides.length > 0 ? 'var(--color-drifted)' : 'inherit'}">${overrides.length}</div>
|
|
171
|
+
<div class="card-note">${overrideRate}% of governed commits</div>
|
|
172
|
+
</div>
|
|
173
|
+
<div class="card">
|
|
174
|
+
<div class="card-label">Session coverage</div>
|
|
175
|
+
<div class="card-value">${sessionCoverage}%</div>
|
|
176
|
+
<div class="card-note">${reposWithSessions} repos with sessions (${sinceDays}d)</div>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
<div class="section">
|
|
181
|
+
<div class="section-title">Significance distribution (fleet)</div>
|
|
182
|
+
${renderSigBars(sigDist)}
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
<div class="section">
|
|
186
|
+
<div class="section-title">Governed commits by repo (${sinceDays}d)</div>
|
|
187
|
+
${renderGoverned(govCommits)}
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
<div class="section">
|
|
191
|
+
<div class="section-title">Override events</div>
|
|
192
|
+
${renderOverrides(overrides)}
|
|
193
|
+
</div>
|
|
194
|
+
`;
|
|
195
|
+
|
|
196
|
+
// Session evidence table
|
|
197
|
+
const sessionSummaries = audit.session_summaries || [];
|
|
198
|
+
if (sessionSummaries.length > 0) {
|
|
199
|
+
html += `
|
|
200
|
+
<div class="section">
|
|
201
|
+
<div class="section-title">Recent session evidence</div>
|
|
202
|
+
<table class="data-table">
|
|
203
|
+
<thead>
|
|
204
|
+
<tr>
|
|
205
|
+
<th>Date</th>
|
|
206
|
+
<th>Repo</th>
|
|
207
|
+
<th>Vendor</th>
|
|
208
|
+
<th>Goal</th>
|
|
209
|
+
<th>Decisions</th>
|
|
210
|
+
<th>Evidence</th>
|
|
211
|
+
</tr>
|
|
212
|
+
</thead>
|
|
213
|
+
<tbody>
|
|
214
|
+
`;
|
|
215
|
+
const displayItems = sessionSummaries.slice(0, 25);
|
|
216
|
+
for (const s of displayItems) {
|
|
217
|
+
const goal = s.goal || "";
|
|
218
|
+
const goalDisplay = goal.length > 60 ? goal.slice(0, 60) + "…" : goal;
|
|
219
|
+
const isSnapshot = window.GATOR_SNAPSHOT;
|
|
220
|
+
|
|
221
|
+
let evidenceCell;
|
|
222
|
+
if (isSnapshot) {
|
|
223
|
+
evidenceCell = `<span class="mono muted" style="font-size:11px">${escHtml(s.source_file || "—")}</span>`;
|
|
224
|
+
} else {
|
|
225
|
+
evidenceCell = `<button class="view-btn" data-session-repo="${escHtml(s.repo)}" data-session-kind="${escHtml(s.source_kind)}" data-session-file="${escHtml(s.source_file)}">View</button>`;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
html += `
|
|
229
|
+
<tr>
|
|
230
|
+
<td class="nowrap">${fmtTs(s.start || s.date)}</td>
|
|
231
|
+
<td>${escHtml(s.repo || "—")}</td>
|
|
232
|
+
<td>${escHtml(s.vendor || "—")}</td>
|
|
233
|
+
<td title="${escHtml(goal)}">${escHtml(goalDisplay || "—")}</td>
|
|
234
|
+
<td class="mono">${s.decisions_count || 0}</td>
|
|
235
|
+
<td>${evidenceCell}</td>
|
|
236
|
+
</tr>
|
|
237
|
+
`;
|
|
238
|
+
}
|
|
239
|
+
html += "</tbody></table></div>";
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Sessions by vendor
|
|
243
|
+
if (sessions.by_vendor && Object.keys(sessions.by_vendor).length > 0) {
|
|
244
|
+
html += `
|
|
245
|
+
<div class="section">
|
|
246
|
+
<div class="section-title">Sessions by agent (${sinceDays}d)</div>
|
|
247
|
+
<table class="data-table">
|
|
248
|
+
<thead><tr><th>Agent</th><th>Sessions</th></tr></thead>
|
|
249
|
+
<tbody>
|
|
250
|
+
`;
|
|
251
|
+
for (const [vendor, count] of Object.entries(sessions.by_vendor)) {
|
|
252
|
+
html += `<tr><td>${escHtml(vendor)}</td><td class="mono">${count}</td></tr>`;
|
|
253
|
+
}
|
|
254
|
+
html += "</tbody></table></div>";
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
container.innerHTML = html;
|
|
258
|
+
|
|
259
|
+
// Delegated click handler for session evidence View buttons
|
|
260
|
+
container.addEventListener("click", function (e) {
|
|
261
|
+
const btn = e.target.closest("[data-session-file]");
|
|
262
|
+
if (!btn) return;
|
|
263
|
+
window.showSessionModal(
|
|
264
|
+
btn.dataset.sessionRepo,
|
|
265
|
+
btn.dataset.sessionKind,
|
|
266
|
+
btn.dataset.sessionFile,
|
|
267
|
+
);
|
|
268
|
+
});
|
|
269
|
+
};
|
|
270
|
+
})();
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fleet.js — Fleet view for Gator Dashboard.
|
|
3
|
+
*
|
|
4
|
+
* Repo operations surface: which repos are governed, their charter
|
|
5
|
+
* state, last activity, and template update status.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
(function () {
|
|
9
|
+
"use strict";
|
|
10
|
+
|
|
11
|
+
window.GatorViews = window.GatorViews || {};
|
|
12
|
+
|
|
13
|
+
// ── helpers ────────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
function escHtml(str) {
|
|
16
|
+
return String(str)
|
|
17
|
+
.replace(/&/g, "&")
|
|
18
|
+
.replace(/</g, "<")
|
|
19
|
+
.replace(/>/g, ">")
|
|
20
|
+
.replace(/"/g, """);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function chartersCell(repo) {
|
|
24
|
+
const count = repo.charters;
|
|
25
|
+
if (count == null) return '<span class="muted">-</span>';
|
|
26
|
+
return `<span title="Click repo name for coverage detail">${count}</span>`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function lastCommitCell(repo) {
|
|
30
|
+
const lc = repo.last_commit;
|
|
31
|
+
if (!lc) return '<span class="muted">-</span>';
|
|
32
|
+
return `<span class="nowrap">${escHtml(lc.age || "unknown")}</span>`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function lastUpdatedCell(repo) {
|
|
36
|
+
const updated = repo.gator_updated;
|
|
37
|
+
if (!updated) return '<span class="muted">-</span>';
|
|
38
|
+
// Format: show date + time if available
|
|
39
|
+
if (updated.includes("T")) {
|
|
40
|
+
const [date, time] = updated.split("T");
|
|
41
|
+
return `<span class="nowrap" style="font-size:12px">${escHtml(date)}<br><span class="muted">${escHtml(time)}</span></span>`;
|
|
42
|
+
}
|
|
43
|
+
return `<span class="nowrap" style="font-size:12px">${escHtml(updated)}</span>`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── render (command-post mode) ──────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
window.GatorViews.fleet = function (data, container) {
|
|
49
|
+
if (data.standalone) {
|
|
50
|
+
renderStandaloneRepos(data, container);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const fleet = data.fleet || {};
|
|
55
|
+
if (fleet.error) {
|
|
56
|
+
container.innerHTML = `<div class="error-block">Fleet data unavailable: ${escHtml(fleet.error)}</div>`;
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const repos = fleet.repos || [];
|
|
61
|
+
const summary = fleet.summary || {};
|
|
62
|
+
const totalRepos = summary.total || repos.length;
|
|
63
|
+
const accessible = summary.accessible || 0;
|
|
64
|
+
const isSnapshot = !!window.GATOR_SNAPSHOT;
|
|
65
|
+
|
|
66
|
+
let html = `
|
|
67
|
+
<div class="view-header">
|
|
68
|
+
<span class="view-title">Fleet</span>
|
|
69
|
+
<span class="view-subtitle">${totalRepos} repos · ${accessible} accessible</span>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div class="section">
|
|
73
|
+
<table class="data-table">
|
|
74
|
+
<thead>
|
|
75
|
+
<tr>
|
|
76
|
+
<th>Repo</th>
|
|
77
|
+
<th>Gator</th>
|
|
78
|
+
<th>Branch</th>
|
|
79
|
+
<th>Charters</th>
|
|
80
|
+
<th>Last commit</th>
|
|
81
|
+
<th>Last updated</th>
|
|
82
|
+
${isSnapshot ? "" : "<th>Status</th>"}
|
|
83
|
+
</tr>
|
|
84
|
+
</thead>
|
|
85
|
+
<tbody>
|
|
86
|
+
`;
|
|
87
|
+
|
|
88
|
+
for (const repo of repos) {
|
|
89
|
+
const accIcon = repo.accessible ? "" : ' <span title="Not accessible locally" class="muted">!</span>';
|
|
90
|
+
const isCommandPost = !!repo.is_command_post;
|
|
91
|
+
const statusCell = isSnapshot ? "" : (isCommandPost
|
|
92
|
+
? `<td class="status-cell"><span class="muted" style="font-size:12px">via Updates</span></td>`
|
|
93
|
+
: `<td class="status-cell"><button class="update-btn" data-repo="${escHtml(repo.name)}" ${repo.accessible ? "" : "disabled"}>Check Status</button><span class="status-result"></span></td>`);
|
|
94
|
+
|
|
95
|
+
html += `
|
|
96
|
+
<tr id="fleet-row-${escHtml(repo.name)}">
|
|
97
|
+
<td>
|
|
98
|
+
<button class="link-btn" onclick="window.gatorNavToRepo('${escHtml(repo.name)}')">${escHtml(repo.name)}</button>
|
|
99
|
+
${accIcon}
|
|
100
|
+
</td>
|
|
101
|
+
<td class="mono" style="font-size:12px">${repo.generation ? 'gen ' + repo.generation : '-'}</td>
|
|
102
|
+
<td class="mono">${escHtml(repo.branch || "-")}</td>
|
|
103
|
+
<td>${chartersCell(repo)}</td>
|
|
104
|
+
<td>${lastCommitCell(repo)}</td>
|
|
105
|
+
<td>${lastUpdatedCell(repo)}</td>
|
|
106
|
+
${statusCell}
|
|
107
|
+
</tr>
|
|
108
|
+
`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (repos.length === 0) {
|
|
112
|
+
html += `<tr><td colspan="${isSnapshot ? 6 : 7}" class="muted" style="text-align:center;padding:24px">No repos found in registry.</td></tr>`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
html += `</tbody></table></div>`;
|
|
116
|
+
container.innerHTML = html;
|
|
117
|
+
|
|
118
|
+
if (!isSnapshot) bindStatusButtons(container);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// ── render (standalone mode) ────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
function renderStandaloneRepos(data, container) {
|
|
124
|
+
const repos = data.repos || [];
|
|
125
|
+
|
|
126
|
+
let html = `
|
|
127
|
+
<div class="view-header">
|
|
128
|
+
<span class="view-title">Fleet</span>
|
|
129
|
+
<span class="view-subtitle">${repos.length} registered</span>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<div class="section">
|
|
133
|
+
<table class="data-table">
|
|
134
|
+
<thead>
|
|
135
|
+
<tr>
|
|
136
|
+
<th>Repo</th>
|
|
137
|
+
<th>Path</th>
|
|
138
|
+
<th>Status</th>
|
|
139
|
+
</tr>
|
|
140
|
+
</thead>
|
|
141
|
+
<tbody>
|
|
142
|
+
`;
|
|
143
|
+
|
|
144
|
+
for (const repo of repos) {
|
|
145
|
+
const accIcon = repo.accessible ? "" : ' <span title="Not accessible" class="muted">!</span>';
|
|
146
|
+
const statusCell = `<td class="status-cell"><button class="update-btn" data-repo="${escHtml(repo.name)}" ${repo.accessible ? "" : "disabled"}>Check Status</button><span class="status-result"></span></td>`;
|
|
147
|
+
|
|
148
|
+
html += `
|
|
149
|
+
<tr>
|
|
150
|
+
<td>
|
|
151
|
+
<button class="link-btn" onclick="window.gatorNavToRepo('${escHtml(repo.name)}')">${escHtml(repo.name)}</button>
|
|
152
|
+
${accIcon}
|
|
153
|
+
</td>
|
|
154
|
+
<td class="mono" style="font-size:12px;max-width:300px;overflow:hidden;text-overflow:ellipsis">${escHtml(repo.path || "-")}</td>
|
|
155
|
+
${statusCell}
|
|
156
|
+
</tr>
|
|
157
|
+
`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (repos.length === 0) {
|
|
161
|
+
html += `<tr><td colspan="3" class="muted" style="text-align:center;padding:24px">No repos registered. Run gatorize.sh on a repo to get started.</td></tr>`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
html += `</tbody></table></div>`;
|
|
165
|
+
container.innerHTML = html;
|
|
166
|
+
bindStatusButtons(container);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ── Check Status / Update flow ──────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
function bindStatusButtons(container) {
|
|
172
|
+
container.querySelectorAll(".update-btn").forEach(btn => {
|
|
173
|
+
btn.addEventListener("click", async function () {
|
|
174
|
+
const repoName = this.dataset.repo;
|
|
175
|
+
const resultSpan = this.nextElementSibling;
|
|
176
|
+
|
|
177
|
+
this.disabled = true;
|
|
178
|
+
this.textContent = "Checking...";
|
|
179
|
+
resultSpan.textContent = "";
|
|
180
|
+
resultSpan.style.color = "";
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
const resp = await fetch(`/api/repo/${encodeURIComponent(repoName)}/check`);
|
|
184
|
+
const data = await resp.json();
|
|
185
|
+
|
|
186
|
+
if (data.error) {
|
|
187
|
+
resultSpan.textContent = data.error;
|
|
188
|
+
resultSpan.style.color = "var(--color-critical)";
|
|
189
|
+
this.textContent = "Check Status";
|
|
190
|
+
this.disabled = false;
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Update Charters cell with health indicator
|
|
195
|
+
updateCharterCell(repoName, data.charter_health);
|
|
196
|
+
|
|
197
|
+
// Count actionable updates
|
|
198
|
+
const plan = data.plan || [];
|
|
199
|
+
const adds = plan.filter(p => p.action === "add").length;
|
|
200
|
+
const updates = plan.filter(p => p.action === "update").length;
|
|
201
|
+
const total = adds + updates;
|
|
202
|
+
|
|
203
|
+
if (total === 0) {
|
|
204
|
+
resultSpan.textContent = "Current";
|
|
205
|
+
resultSpan.style.color = "var(--color-healthy)";
|
|
206
|
+
this.textContent = "Check Status";
|
|
207
|
+
this.disabled = false;
|
|
208
|
+
} else {
|
|
209
|
+
// Transform into Update button
|
|
210
|
+
resultSpan.textContent = `${total} update${total > 1 ? "s" : ""} available`;
|
|
211
|
+
resultSpan.style.color = "var(--color-drifted, orange)";
|
|
212
|
+
this.textContent = "Update";
|
|
213
|
+
this.disabled = false;
|
|
214
|
+
// Replace click handler for the update action
|
|
215
|
+
this.replaceWith(createUpdateButton(repoName, resultSpan));
|
|
216
|
+
}
|
|
217
|
+
} catch (err) {
|
|
218
|
+
resultSpan.textContent = "Error";
|
|
219
|
+
resultSpan.style.color = "var(--color-critical)";
|
|
220
|
+
this.textContent = "Check Status";
|
|
221
|
+
this.disabled = false;
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function updateCharterCell(repoName, charterHealth) {
|
|
228
|
+
const row = document.getElementById(`fleet-row-${repoName}`);
|
|
229
|
+
if (!row) return;
|
|
230
|
+
// Charters is the 4th column (index 3) after Repo, Gator, Branch
|
|
231
|
+
const cells = row.querySelectorAll("td");
|
|
232
|
+
if (cells.length < 4) return;
|
|
233
|
+
const cell = cells[3];
|
|
234
|
+
const currentText = cell.textContent.trim();
|
|
235
|
+
const count = parseInt(currentText) || currentText;
|
|
236
|
+
|
|
237
|
+
if (!charterHealth || charterHealth.finding_count < 0) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (charterHealth.finding_count === 0) {
|
|
241
|
+
cell.innerHTML = `<span style="color:var(--color-healthy);cursor:default" title="All functions and modules covered by charters.">${escHtml(String(count))} ✓</span>`;
|
|
242
|
+
} else {
|
|
243
|
+
const findings = charterHealth.findings || [];
|
|
244
|
+
const fc = charterHealth.finding_count;
|
|
245
|
+
// Build tooltip from findings
|
|
246
|
+
const tooltip = findings.slice(0, 8).map(f => {
|
|
247
|
+
const cls = f["class"] || f.type || "";
|
|
248
|
+
const msg = f.message || f.detail || JSON.stringify(f);
|
|
249
|
+
return cls ? `${cls}: ${msg}` : msg;
|
|
250
|
+
}).join("\n");
|
|
251
|
+
const more = fc > 8 ? `\n... and ${fc - 8} more` : "";
|
|
252
|
+
cell.innerHTML = `<span style="color:var(--color-drifted, orange);cursor:help" title="${escHtml(tooltip + more)}">${escHtml(String(count))} !</span>`;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function createUpdateButton(repoName, resultSpan) {
|
|
257
|
+
const btn = document.createElement("button");
|
|
258
|
+
btn.className = "update-btn";
|
|
259
|
+
btn.textContent = "Update";
|
|
260
|
+
btn.dataset.repo = repoName;
|
|
261
|
+
btn.addEventListener("click", async function () {
|
|
262
|
+
this.disabled = true;
|
|
263
|
+
this.textContent = "Updating...";
|
|
264
|
+
resultSpan.textContent = "";
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
const resp = await fetch(`/api/repo/${encodeURIComponent(repoName)}/update`, {
|
|
268
|
+
method: "POST",
|
|
269
|
+
headers: { "X-Gator-Dashboard": "1" },
|
|
270
|
+
});
|
|
271
|
+
const data = await resp.json();
|
|
272
|
+
|
|
273
|
+
if (data.status === "ok") {
|
|
274
|
+
resultSpan.textContent = parseSummaryLine(data.output);
|
|
275
|
+
resultSpan.style.color = "var(--color-healthy)";
|
|
276
|
+
if (window.gatorRefreshFleet) window.gatorRefreshFleet();
|
|
277
|
+
} else {
|
|
278
|
+
resultSpan.textContent = "Failed";
|
|
279
|
+
resultSpan.style.color = "var(--color-critical)";
|
|
280
|
+
resultSpan.title = data.output || data.error || "";
|
|
281
|
+
}
|
|
282
|
+
} catch (err) {
|
|
283
|
+
resultSpan.textContent = "Error";
|
|
284
|
+
resultSpan.style.color = "var(--color-critical)";
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Replace with a fresh Check Status button
|
|
288
|
+
const freshBtn = document.createElement("button");
|
|
289
|
+
freshBtn.className = "update-btn";
|
|
290
|
+
freshBtn.textContent = "Check Status";
|
|
291
|
+
freshBtn.dataset.repo = repoName;
|
|
292
|
+
this.replaceWith(freshBtn);
|
|
293
|
+
bindStatusButtons(freshBtn.parentElement.closest("table") || freshBtn.parentElement);
|
|
294
|
+
});
|
|
295
|
+
return btn;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function parseSummaryLine(output) {
|
|
299
|
+
if (!output) return "Done";
|
|
300
|
+
const lines = output.split("\n").map(l => l.trim()).filter(Boolean);
|
|
301
|
+
const done = lines.find(l => l.startsWith("Done:"));
|
|
302
|
+
if (done) return done;
|
|
303
|
+
const current = lines.find(l => l.includes("Everything is current"));
|
|
304
|
+
if (current) return "Already current";
|
|
305
|
+
return "Done";
|
|
306
|
+
}
|
|
307
|
+
})();
|