@julioborges/gantry 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/gantry/SKILL.md +10 -1
- package/.agents/skills/gantry/dashboard/static/app.js +90 -24
- package/.agents/skills/gantry/dashboard/static/index.html +57 -7
- package/.agents/skills/gantry/dashboard/static/style.css +469 -36
- package/.agents/skills/gantry/hooks/antigravity.hooks.json +2 -2
- package/.agents/skills/gantry/scripts/guard.py +6 -0
- package/.agents/skills/gantry/scripts/setup.py +1 -1
- package/package.json +1 -1
|
@@ -124,7 +124,7 @@ Every script provides `--help`, and data-producing paths support `--json`.
|
|
|
124
124
|
`issue.blocked` records only a Run-log fact: the Issue's `Status:` line stays `ready-for-agent` so
|
|
125
125
|
`frontier.py` keeps offering it, and only `roadmap.py done` after Critic acceptance ever changes an
|
|
126
126
|
Issue's authoritative status. Omitting `runId` or `unitId` disables recording entirely and leaves the
|
|
127
|
-
round behaviorally identical
|
|
127
|
+
round behaviorally identical (a headless/test fallback only; conversational agents must not omit them).
|
|
128
128
|
- Before any agent works in a worktree, the round workflow marks it with the Run — `runlog.py mark
|
|
129
129
|
<runId> --cwd <worktree>`, stored in that worktree's own git directory — and clears it with
|
|
130
130
|
`runlog.py unmark` when the Run ends, never between rounds. The tracked git hooks
|
|
@@ -187,6 +187,15 @@ command's stdin, not appended to the command line. Every recorded Run event goes
|
|
|
187
187
|
`runlog.py append <unitId> <runId>` this way, with the event JSON as `input` — a harness that implements
|
|
188
188
|
`runCommand` without stdin support breaks every recorded round, not just this one.
|
|
189
189
|
|
|
190
|
+
When coordinating directly in conversational or manual harnesses (Antigravity, Cursor, interactive CLI),
|
|
191
|
+
the coordinating agent MUST explicitly record Run lifecycle events via the CLI:
|
|
192
|
+
1. **Preflight**: resolve `unitId` (`python3 <skillDir>/scripts/runlog.py unit-id --cwd <repoRoot>`) and generate `runId="run-$(date +%s)"`.
|
|
193
|
+
2. **Start Run**: append `run.started` with repository root, policy hash, tier, and staleAfterSeconds.
|
|
194
|
+
3. **Mark worktree**: mark the active worktree with `python3 <skillDir>/scripts/runlog.py mark <runId> --cwd <worktree>`.
|
|
195
|
+
4. **Rounds and phases**: append `round.started`, and before/after each phase append `phase.started` and `phase.finished` with `{ issue, phase }`.
|
|
196
|
+
5. **Completion**: on issue completion after Critic acceptance, append `issue.done`. On Run completion, unmark with `python3 <skillDir>/scripts/runlog.py unmark --cwd <worktree>` and append `run.finished`.
|
|
197
|
+
Never omit `runId` or `unitId` during interactive execution; doing so disables telemetry and leaves the dashboard blind to active runs.
|
|
198
|
+
|
|
190
199
|
## References
|
|
191
200
|
|
|
192
201
|
- `reference/plan-workflow.md` — structural validation, Requirement Critic, research, draft, plan
|
|
@@ -16,26 +16,40 @@
|
|
|
16
16
|
const card = document.createElement("div");
|
|
17
17
|
card.className = "card";
|
|
18
18
|
|
|
19
|
-
const title = document.createElement("
|
|
20
|
-
title.
|
|
19
|
+
const title = document.createElement("div");
|
|
20
|
+
title.className = "card-title";
|
|
21
|
+
const titleText = document.createElement("span");
|
|
22
|
+
titleText.textContent = issue.issue;
|
|
23
|
+
title.appendChild(titleText);
|
|
24
|
+
|
|
25
|
+
if (issue.operatorWaiting) {
|
|
26
|
+
title.appendChild(badge("AWAITING OPERATOR", "waiting"));
|
|
27
|
+
}
|
|
21
28
|
card.appendChild(title);
|
|
22
|
-
card.appendChild(document.createElement("br"));
|
|
23
29
|
|
|
24
|
-
|
|
25
|
-
|
|
30
|
+
const badgesContainer = document.createElement("div");
|
|
31
|
+
badgesContainer.className = "card-badges";
|
|
32
|
+
|
|
33
|
+
if (issue.branch) {
|
|
34
|
+
badgesContainer.appendChild(badge("br: " + issue.branch, "branch"));
|
|
35
|
+
}
|
|
36
|
+
if (issue.worktree) {
|
|
37
|
+
badgesContainer.appendChild(badge("wt: " + issue.worktree, "branch"));
|
|
38
|
+
}
|
|
26
39
|
Object.keys(issue.models || {}).forEach(function (role) {
|
|
27
|
-
|
|
40
|
+
badgesContainer.appendChild(badge(role + ": " + issue.models[role], "model"));
|
|
28
41
|
});
|
|
29
42
|
if (issue.correctionBudget) {
|
|
30
|
-
|
|
31
|
-
badge("corrections: " + issue.correctionBudget.used + "/" + issue.correctionBudget.ceiling)
|
|
43
|
+
badgesContainer.appendChild(
|
|
44
|
+
badge("corrections: " + issue.correctionBudget.used + "/" + issue.correctionBudget.ceiling, "budget")
|
|
32
45
|
);
|
|
33
46
|
}
|
|
34
47
|
if (typeof issue.elapsedPhaseSeconds === "number") {
|
|
35
|
-
|
|
48
|
+
badgesContainer.appendChild(badge(issue.elapsedPhaseSeconds + "s", "elapsed"));
|
|
36
49
|
}
|
|
37
|
-
|
|
38
|
-
|
|
50
|
+
|
|
51
|
+
if (badgesContainer.children.length > 0) {
|
|
52
|
+
card.appendChild(badgesContainer);
|
|
39
53
|
}
|
|
40
54
|
return card;
|
|
41
55
|
}
|
|
@@ -44,31 +58,68 @@
|
|
|
44
58
|
const section = document.createElement("section");
|
|
45
59
|
section.className = "swimlane" + (run.stale ? " stale" : "");
|
|
46
60
|
|
|
61
|
+
const header = document.createElement("div");
|
|
62
|
+
header.className = "swimlane-header";
|
|
63
|
+
|
|
64
|
+
const titleGroup = document.createElement("div");
|
|
65
|
+
titleGroup.className = "swimlane-title-group";
|
|
66
|
+
|
|
47
67
|
const heading = document.createElement("h2");
|
|
48
|
-
heading.textContent =
|
|
49
|
-
|
|
50
|
-
|
|
68
|
+
heading.textContent = run.repositoryRoot + " — " + run.run;
|
|
69
|
+
titleGroup.appendChild(heading);
|
|
70
|
+
|
|
71
|
+
const meta = document.createElement("div");
|
|
72
|
+
meta.className = "swimlane-meta";
|
|
73
|
+
|
|
74
|
+
meta.appendChild(badge("tier: " + run.tier, "tier"));
|
|
75
|
+
|
|
76
|
+
if (run.stale) {
|
|
77
|
+
meta.appendChild(badge("STALE", "stale"));
|
|
78
|
+
} else {
|
|
79
|
+
meta.appendChild(badge("LIVE", "live"));
|
|
80
|
+
}
|
|
51
81
|
|
|
52
82
|
if (run.compactionAt) {
|
|
53
|
-
|
|
83
|
+
meta.appendChild(badge("compacted: " + run.compactionAt, "compaction"));
|
|
54
84
|
}
|
|
55
85
|
|
|
86
|
+
header.appendChild(titleGroup);
|
|
87
|
+
header.appendChild(meta);
|
|
88
|
+
section.appendChild(header);
|
|
89
|
+
|
|
56
90
|
const columnsEl = document.createElement("div");
|
|
57
91
|
columnsEl.className = "columns";
|
|
58
92
|
|
|
59
93
|
columns.forEach(function (columnName) {
|
|
60
94
|
const columnEl = document.createElement("div");
|
|
61
95
|
columnEl.className = "column";
|
|
62
|
-
|
|
96
|
+
|
|
97
|
+
const colHeader = document.createElement("div");
|
|
98
|
+
colHeader.className = "column-header";
|
|
99
|
+
|
|
100
|
+
const title = document.createElement("span");
|
|
101
|
+
title.className = "column-title";
|
|
63
102
|
title.textContent = columnName;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
103
|
+
colHeader.appendChild(title);
|
|
104
|
+
|
|
105
|
+
const matchingIssues = run.issues.filter(function (issue) {
|
|
106
|
+
return issue.column === columnName;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const count = document.createElement("span");
|
|
110
|
+
count.className = "column-count";
|
|
111
|
+
count.textContent = matchingIssues.length;
|
|
112
|
+
colHeader.appendChild(count);
|
|
113
|
+
|
|
114
|
+
columnEl.appendChild(colHeader);
|
|
115
|
+
|
|
116
|
+
const cardsContainer = document.createElement("div");
|
|
117
|
+
cardsContainer.className = "column-cards";
|
|
118
|
+
matchingIssues.forEach(function (issue) {
|
|
119
|
+
cardsContainer.appendChild(renderCard(issue));
|
|
120
|
+
});
|
|
121
|
+
columnEl.appendChild(cardsContainer);
|
|
122
|
+
|
|
72
123
|
columnsEl.appendChild(columnEl);
|
|
73
124
|
});
|
|
74
125
|
|
|
@@ -79,6 +130,21 @@
|
|
|
79
130
|
function render(state) {
|
|
80
131
|
const root = document.getElementById("swimlanes");
|
|
81
132
|
root.textContent = "";
|
|
133
|
+
|
|
134
|
+
if (!state.runs || state.runs.length === 0) {
|
|
135
|
+
const empty = document.createElement("div");
|
|
136
|
+
empty.className = "empty-state";
|
|
137
|
+
const title = document.createElement("div");
|
|
138
|
+
title.className = "empty-state-title";
|
|
139
|
+
title.textContent = "NO ACTIVE RUNS DETECTED";
|
|
140
|
+
empty.appendChild(title);
|
|
141
|
+
const desc = document.createElement("p");
|
|
142
|
+
desc.textContent = "No execution units found in ~/.gantry/state.";
|
|
143
|
+
empty.appendChild(desc);
|
|
144
|
+
root.appendChild(empty);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
82
148
|
state.runs.forEach(function (run) {
|
|
83
149
|
root.appendChild(renderRun(run, state.columns));
|
|
84
150
|
});
|
|
@@ -1,16 +1,66 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
2
|
+
<html lang="en" class="dark">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
|
-
<
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Gantry Dashboard // Telemetry & Run Log</title>
|
|
6
7
|
<link rel="stylesheet" href="/style.css" />
|
|
7
8
|
</head>
|
|
8
|
-
<body>
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
<
|
|
9
|
+
<body class="industrial-grid">
|
|
10
|
+
<!-- Industrial Header / Rig Bar -->
|
|
11
|
+
<header class="rig-header">
|
|
12
|
+
<div class="header-inner">
|
|
13
|
+
<div class="brand-group">
|
|
14
|
+
<div class="brand-mark">
|
|
15
|
+
<span class="diamond"></span>
|
|
16
|
+
<span class="brand-title">GANTRY<span class="accent">::</span>DASHBOARD</span>
|
|
17
|
+
</div>
|
|
18
|
+
<span class="telemetry-badge">v1.0.4 [STANDBY]</span>
|
|
19
|
+
</div>
|
|
20
|
+
<div class="header-status">
|
|
21
|
+
<span class="status-indicator"></span>
|
|
22
|
+
<span class="status-text">LOOPBACK TELEMETRY // READ-ONLY</span>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
12
25
|
</header>
|
|
13
|
-
|
|
26
|
+
|
|
27
|
+
<!-- Sub-header / System Status Strip -->
|
|
28
|
+
<div class="status-strip">
|
|
29
|
+
<div class="status-strip-inner">
|
|
30
|
+
<div class="status-strip-left">
|
|
31
|
+
<span class="signal-dot"></span>
|
|
32
|
+
<span class="strip-label">SYSTEM RIG // MULTI-RUN KANBAN ACTIVE</span>
|
|
33
|
+
</div>
|
|
34
|
+
<div class="status-strip-badges">
|
|
35
|
+
<span class="badge-tag">STATUS: RUNNING</span>
|
|
36
|
+
<span class="badge-tag">HARNESS-NEUTRAL</span>
|
|
37
|
+
<span class="badge-tag accent-tag">ZERO MUTATION</span>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<!-- Main Container -->
|
|
43
|
+
<main class="dashboard-main">
|
|
44
|
+
<div id="swimlanes"></div>
|
|
45
|
+
</main>
|
|
46
|
+
|
|
47
|
+
<!-- Telemetry Footer -->
|
|
48
|
+
<footer class="rig-footer">
|
|
49
|
+
<div class="footer-inner">
|
|
50
|
+
<div class="footer-left">
|
|
51
|
+
<span class="accent-diamond">◆</span>
|
|
52
|
+
<span>GANTRY AGENTIC SDLC</span>
|
|
53
|
+
<span>|</span>
|
|
54
|
+
<span>APACHE-2.0</span>
|
|
55
|
+
<span>|</span>
|
|
56
|
+
<span>DETERMINISTIC VERIFICATION</span>
|
|
57
|
+
</div>
|
|
58
|
+
<div class="footer-right">
|
|
59
|
+
<span>OPERATOR IN LOOP // READ-ONLY DISPATCH</span>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</footer>
|
|
63
|
+
|
|
14
64
|
<script src="/app.js"></script>
|
|
15
65
|
</body>
|
|
16
66
|
</html>
|
|
@@ -1,74 +1,507 @@
|
|
|
1
|
+
/* Gantry Dashboard Industrial Dark Theme — Matching Gantry Site Visual Language */
|
|
2
|
+
|
|
1
3
|
:root {
|
|
2
|
-
|
|
3
|
-
--
|
|
4
|
+
--obsidian-950: #05070a;
|
|
5
|
+
--obsidian-900: #0a0d14;
|
|
6
|
+
--obsidian-850: #0f141f;
|
|
7
|
+
--obsidian-800: #141a29;
|
|
8
|
+
--obsidian-700: #1f293d;
|
|
9
|
+
--obsidian-600: #2a3752;
|
|
10
|
+
|
|
11
|
+
--amber-glow: #ff9900;
|
|
12
|
+
--amber-accent: #f59e0b;
|
|
13
|
+
--amber-dim: rgba(255, 153, 0, 0.15);
|
|
14
|
+
|
|
15
|
+
--slate-100: #f1f5f9;
|
|
16
|
+
--slate-200: #e2e8f0;
|
|
17
|
+
--slate-300: #cbd5e1;
|
|
18
|
+
--slate-400: #94a3b8;
|
|
19
|
+
--slate-500: #64748b;
|
|
20
|
+
|
|
21
|
+
--red-alert: #ef4444;
|
|
22
|
+
--red-dim: rgba(239, 68, 68, 0.15);
|
|
23
|
+
--green-ok: #10b981;
|
|
24
|
+
--green-dim: rgba(16, 185, 129, 0.15);
|
|
25
|
+
--purple-accent: #a855f7;
|
|
26
|
+
--purple-dim: rgba(168, 85, 247, 0.15);
|
|
27
|
+
|
|
28
|
+
--font-mono: "JetBrains Mono", "Fira Code", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
* {
|
|
32
|
+
box-sizing: border-box;
|
|
33
|
+
margin: 0;
|
|
34
|
+
padding: 0;
|
|
4
35
|
}
|
|
5
36
|
|
|
6
37
|
body {
|
|
7
|
-
|
|
8
|
-
|
|
38
|
+
background-color: var(--obsidian-900);
|
|
39
|
+
color: var(--slate-300);
|
|
40
|
+
font-family: var(--font-mono);
|
|
41
|
+
font-size: 13px;
|
|
42
|
+
line-height: 1.5;
|
|
43
|
+
min-height: 100vh;
|
|
44
|
+
display: flex;
|
|
45
|
+
flex-col: column;
|
|
46
|
+
flex-direction: column;
|
|
47
|
+
overflow-x: hidden;
|
|
48
|
+
-webkit-font-smoothing: antialiased;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* Technical Grid Pattern */
|
|
52
|
+
.industrial-grid {
|
|
53
|
+
background-size: 32px 32px;
|
|
54
|
+
background-image:
|
|
55
|
+
linear-gradient(to right, rgba(255, 153, 0, 0.04) 1px, transparent 1px),
|
|
56
|
+
linear-gradient(to bottom, rgba(255, 153, 0, 0.04) 1px, transparent 1px);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* Scrollbars */
|
|
60
|
+
::-webkit-scrollbar {
|
|
61
|
+
width: 8px;
|
|
62
|
+
height: 8px;
|
|
63
|
+
}
|
|
64
|
+
::-webkit-scrollbar-track {
|
|
65
|
+
background: var(--obsidian-900);
|
|
66
|
+
border-left: 1px solid var(--obsidian-700);
|
|
67
|
+
}
|
|
68
|
+
::-webkit-scrollbar-thumb {
|
|
69
|
+
background: var(--obsidian-700);
|
|
70
|
+
border-radius: 2px;
|
|
71
|
+
}
|
|
72
|
+
::-webkit-scrollbar-thumb:hover {
|
|
73
|
+
background: var(--amber-accent);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* Header */
|
|
77
|
+
.rig-header {
|
|
78
|
+
position: sticky;
|
|
79
|
+
top: 0;
|
|
80
|
+
z-index: 50;
|
|
81
|
+
border-bottom: 1px solid var(--obsidian-700);
|
|
82
|
+
background-color: rgba(10, 13, 20, 0.95);
|
|
83
|
+
backdrop-filter: blur(8px);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.header-inner {
|
|
87
|
+
max-width: 1600px;
|
|
88
|
+
margin: 0 auto;
|
|
89
|
+
padding: 0 1.5rem;
|
|
90
|
+
height: 3.5rem;
|
|
91
|
+
display: flex;
|
|
92
|
+
align-items: center;
|
|
93
|
+
justify-content: space-between;
|
|
94
|
+
gap: 1rem;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.brand-group {
|
|
98
|
+
display: flex;
|
|
99
|
+
align-items: center;
|
|
100
|
+
gap: 0.75rem;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.brand-mark {
|
|
104
|
+
display: flex;
|
|
105
|
+
align-items: center;
|
|
106
|
+
gap: 0.5rem;
|
|
107
|
+
font-weight: 700;
|
|
108
|
+
font-size: 1rem;
|
|
109
|
+
letter-spacing: 0.05em;
|
|
110
|
+
color: var(--slate-100);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.diamond {
|
|
114
|
+
display: inline-block;
|
|
115
|
+
width: 10px;
|
|
116
|
+
height: 10px;
|
|
117
|
+
background-color: var(--amber-glow);
|
|
118
|
+
transform: rotate(45deg);
|
|
119
|
+
box-shadow: 0 0 8px rgba(255, 153, 0, 0.6);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.accent {
|
|
123
|
+
color: var(--amber-accent);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.telemetry-badge {
|
|
127
|
+
display: inline-flex;
|
|
128
|
+
align-items: center;
|
|
129
|
+
padding: 0.15rem 0.5rem;
|
|
130
|
+
font-size: 11px;
|
|
131
|
+
background-color: var(--obsidian-800);
|
|
132
|
+
color: var(--amber-accent);
|
|
133
|
+
border: 1px solid var(--obsidian-700);
|
|
134
|
+
font-weight: 600;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.header-status {
|
|
138
|
+
display: flex;
|
|
139
|
+
align-items: center;
|
|
140
|
+
gap: 0.5rem;
|
|
141
|
+
font-size: 11px;
|
|
142
|
+
color: var(--slate-400);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.status-indicator {
|
|
146
|
+
width: 8px;
|
|
147
|
+
height: 8px;
|
|
148
|
+
background-color: var(--amber-glow);
|
|
149
|
+
border-radius: 50%;
|
|
150
|
+
box-shadow: 0 0 8px var(--amber-glow);
|
|
151
|
+
animation: pulse-glow 2s infinite ease-in-out;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
@keyframes pulse-glow {
|
|
155
|
+
0%, 100% { opacity: 1; transform: scale(1); }
|
|
156
|
+
50% { opacity: 0.4; transform: scale(0.85); }
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/* Status Sub-strip */
|
|
160
|
+
.status-strip {
|
|
161
|
+
border-bottom: 1px solid var(--obsidian-700);
|
|
162
|
+
background-color: rgba(5, 7, 10, 0.8);
|
|
163
|
+
font-size: 11px;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.status-strip-inner {
|
|
167
|
+
max-width: 1600px;
|
|
168
|
+
margin: 0 auto;
|
|
169
|
+
padding: 0.5rem 1.5rem;
|
|
170
|
+
display: flex;
|
|
171
|
+
flex-wrap: wrap;
|
|
172
|
+
align-items: center;
|
|
173
|
+
justify-content: space-between;
|
|
174
|
+
gap: 0.75rem;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.status-strip-left {
|
|
178
|
+
display: flex;
|
|
179
|
+
align-items: center;
|
|
180
|
+
gap: 0.5rem;
|
|
181
|
+
color: var(--amber-accent);
|
|
182
|
+
font-weight: 600;
|
|
9
183
|
}
|
|
10
184
|
|
|
11
|
-
|
|
12
|
-
|
|
185
|
+
.signal-dot {
|
|
186
|
+
width: 6px;
|
|
187
|
+
height: 6px;
|
|
188
|
+
background-color: var(--amber-glow);
|
|
13
189
|
}
|
|
14
190
|
|
|
191
|
+
.status-strip-badges {
|
|
192
|
+
display: flex;
|
|
193
|
+
flex-wrap: wrap;
|
|
194
|
+
align-items: center;
|
|
195
|
+
gap: 0.5rem;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.badge-tag {
|
|
199
|
+
padding: 0.15rem 0.5rem;
|
|
200
|
+
background-color: var(--obsidian-900);
|
|
201
|
+
border: 1px solid var(--obsidian-700);
|
|
202
|
+
color: var(--slate-400);
|
|
203
|
+
font-size: 10px;
|
|
204
|
+
text-transform: uppercase;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.badge-tag.accent-tag {
|
|
208
|
+
border-color: rgba(245, 158, 11, 0.5);
|
|
209
|
+
color: var(--amber-glow);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/* Main Workspace */
|
|
213
|
+
.dashboard-main {
|
|
214
|
+
flex: 1;
|
|
215
|
+
max-width: 1600px;
|
|
216
|
+
width: 100%;
|
|
217
|
+
margin: 0 auto;
|
|
218
|
+
padding: 1.5rem;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/* Empty state */
|
|
222
|
+
.empty-state {
|
|
223
|
+
border: 1px dashed var(--obsidian-700);
|
|
224
|
+
background-color: var(--obsidian-950);
|
|
225
|
+
padding: 3rem 2rem;
|
|
226
|
+
text-align: center;
|
|
227
|
+
color: var(--slate-500);
|
|
228
|
+
}
|
|
229
|
+
.empty-state-title {
|
|
230
|
+
color: var(--amber-accent);
|
|
231
|
+
font-size: 14px;
|
|
232
|
+
font-weight: 700;
|
|
233
|
+
margin-bottom: 0.5rem;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/* Swimlane / Execution Rig */
|
|
15
237
|
.swimlane {
|
|
16
|
-
border: 1px solid var(--
|
|
17
|
-
|
|
18
|
-
margin-bottom:
|
|
19
|
-
|
|
238
|
+
border: 1px solid var(--obsidian-700);
|
|
239
|
+
background-color: rgba(15, 20, 31, 0.85);
|
|
240
|
+
margin-bottom: 2rem;
|
|
241
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
|
|
242
|
+
position: relative;
|
|
243
|
+
transition: border-color 0.2s ease;
|
|
20
244
|
}
|
|
21
245
|
|
|
22
246
|
.swimlane.stale {
|
|
23
|
-
border-color:
|
|
247
|
+
border-color: var(--red-alert);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.swimlane.stale .swimlane-header {
|
|
251
|
+
border-bottom-color: var(--red-alert);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/* Corner brackets on swimlane (matching site rig style) */
|
|
255
|
+
.swimlane::before,
|
|
256
|
+
.swimlane::after {
|
|
257
|
+
content: "";
|
|
258
|
+
position: absolute;
|
|
259
|
+
width: 8px;
|
|
260
|
+
height: 8px;
|
|
261
|
+
pointer-events: none;
|
|
262
|
+
}
|
|
263
|
+
.swimlane::before {
|
|
264
|
+
top: -1px;
|
|
265
|
+
left: -1px;
|
|
266
|
+
border-top: 2px solid var(--amber-glow);
|
|
267
|
+
border-left: 2px solid var(--amber-glow);
|
|
268
|
+
}
|
|
269
|
+
.swimlane::after {
|
|
270
|
+
bottom: -1px;
|
|
271
|
+
right: -1px;
|
|
272
|
+
border-bottom: 2px solid var(--amber-glow);
|
|
273
|
+
border-right: 2px solid var(--amber-glow);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.swimlane-header {
|
|
277
|
+
padding: 0.75rem 1rem;
|
|
278
|
+
border-bottom: 1px solid var(--obsidian-700);
|
|
279
|
+
background-color: var(--obsidian-950);
|
|
280
|
+
display: flex;
|
|
281
|
+
flex-wrap: wrap;
|
|
282
|
+
align-items: center;
|
|
283
|
+
justify-content: space-between;
|
|
284
|
+
gap: 0.75rem;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.swimlane-title-group {
|
|
288
|
+
display: flex;
|
|
289
|
+
flex-wrap: wrap;
|
|
290
|
+
align-items: center;
|
|
291
|
+
gap: 0.5rem;
|
|
24
292
|
}
|
|
25
293
|
|
|
26
294
|
.swimlane h2 {
|
|
27
|
-
|
|
28
|
-
font-
|
|
295
|
+
font-size: 12px;
|
|
296
|
+
font-weight: 700;
|
|
297
|
+
color: var(--slate-100);
|
|
298
|
+
letter-spacing: 0.03em;
|
|
299
|
+
text-transform: uppercase;
|
|
29
300
|
}
|
|
30
301
|
|
|
302
|
+
.swimlane-meta {
|
|
303
|
+
display: flex;
|
|
304
|
+
flex-wrap: wrap;
|
|
305
|
+
align-items: center;
|
|
306
|
+
gap: 0.5rem;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/* Kanban Columns Grid */
|
|
31
310
|
.columns {
|
|
32
311
|
display: grid;
|
|
33
|
-
grid-template-columns: repeat(8, minmax(
|
|
34
|
-
gap:
|
|
312
|
+
grid-template-columns: repeat(8, minmax(130px, 1fr));
|
|
313
|
+
gap: 1px;
|
|
314
|
+
background-color: var(--obsidian-700);
|
|
315
|
+
overflow-x: auto;
|
|
35
316
|
}
|
|
36
317
|
|
|
37
318
|
.column {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
padding: 0.5rem;
|
|
41
|
-
|
|
319
|
+
background-color: var(--obsidian-900);
|
|
320
|
+
min-height: 180px;
|
|
321
|
+
padding: 0.65rem 0.5rem;
|
|
322
|
+
display: flex;
|
|
323
|
+
flex-direction: column;
|
|
42
324
|
}
|
|
43
325
|
|
|
44
|
-
.column
|
|
45
|
-
|
|
46
|
-
|
|
326
|
+
.column-header {
|
|
327
|
+
display: flex;
|
|
328
|
+
align-items: center;
|
|
329
|
+
justify-content: space-between;
|
|
330
|
+
margin-bottom: 0.65rem;
|
|
331
|
+
padding-bottom: 0.4rem;
|
|
332
|
+
border-bottom: 1px solid var(--obsidian-800);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.column-title {
|
|
336
|
+
font-size: 10px;
|
|
337
|
+
font-weight: 700;
|
|
47
338
|
text-transform: uppercase;
|
|
48
|
-
|
|
339
|
+
letter-spacing: 0.08em;
|
|
340
|
+
color: var(--slate-400);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.column-count {
|
|
344
|
+
font-size: 9px;
|
|
345
|
+
padding: 0.1rem 0.35rem;
|
|
346
|
+
background-color: var(--obsidian-800);
|
|
347
|
+
border: 1px solid var(--obsidian-700);
|
|
348
|
+
color: var(--slate-500);
|
|
349
|
+
font-weight: 600;
|
|
49
350
|
}
|
|
50
351
|
|
|
352
|
+
.column-cards {
|
|
353
|
+
display: flex;
|
|
354
|
+
flex-direction: column;
|
|
355
|
+
gap: 0.5rem;
|
|
356
|
+
flex: 1;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/* Card Rig */
|
|
51
360
|
.card {
|
|
52
|
-
border: 1px solid var(--
|
|
53
|
-
|
|
54
|
-
padding: 0.
|
|
55
|
-
|
|
56
|
-
|
|
361
|
+
border: 1px solid var(--obsidian-700);
|
|
362
|
+
background-color: var(--obsidian-950);
|
|
363
|
+
padding: 0.6rem;
|
|
364
|
+
transition: all 0.15s ease;
|
|
365
|
+
position: relative;
|
|
57
366
|
}
|
|
58
367
|
|
|
368
|
+
.card:hover {
|
|
369
|
+
border-color: var(--amber-accent);
|
|
370
|
+
transform: translateY(-1px);
|
|
371
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.card-title {
|
|
375
|
+
display: flex;
|
|
376
|
+
align-items: center;
|
|
377
|
+
justify-content: space-between;
|
|
378
|
+
font-size: 11px;
|
|
379
|
+
font-weight: 700;
|
|
380
|
+
color: var(--slate-100);
|
|
381
|
+
margin-bottom: 0.45rem;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.card-badges {
|
|
385
|
+
display: flex;
|
|
386
|
+
flex-wrap: wrap;
|
|
387
|
+
gap: 0.25rem;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/* Badges */
|
|
59
391
|
.badge {
|
|
60
|
-
display: inline-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
border: 1px solid var(--
|
|
392
|
+
display: inline-flex;
|
|
393
|
+
align-items: center;
|
|
394
|
+
font-size: 9px;
|
|
395
|
+
font-weight: 500;
|
|
396
|
+
padding: 0.15rem 0.35rem;
|
|
397
|
+
border: 1px solid var(--obsidian-700);
|
|
398
|
+
background-color: var(--obsidian-850);
|
|
399
|
+
color: var(--slate-300);
|
|
400
|
+
text-transform: uppercase;
|
|
401
|
+
letter-spacing: 0.02em;
|
|
402
|
+
line-height: 1;
|
|
403
|
+
white-space: nowrap;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.badge.tier {
|
|
407
|
+
border-color: var(--amber-glow);
|
|
408
|
+
color: var(--amber-glow);
|
|
409
|
+
background-color: var(--amber-dim);
|
|
410
|
+
font-weight: 700;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
.badge.stale {
|
|
414
|
+
border-color: var(--red-alert);
|
|
415
|
+
color: var(--red-alert);
|
|
416
|
+
background-color: var(--red-dim);
|
|
417
|
+
font-weight: 700;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
.badge.live {
|
|
421
|
+
border-color: var(--green-ok);
|
|
422
|
+
color: var(--green-ok);
|
|
423
|
+
background-color: var(--green-dim);
|
|
66
424
|
}
|
|
67
425
|
|
|
68
426
|
.badge.waiting {
|
|
69
|
-
border-color:
|
|
427
|
+
border-color: var(--amber-glow);
|
|
428
|
+
color: var(--amber-glow);
|
|
429
|
+
background-color: var(--amber-dim);
|
|
430
|
+
animation: pulse-glow 1.5s infinite;
|
|
431
|
+
font-weight: 700;
|
|
70
432
|
}
|
|
71
433
|
|
|
72
434
|
.badge.compaction {
|
|
73
|
-
border-color:
|
|
435
|
+
border-color: var(--purple-accent);
|
|
436
|
+
color: var(--purple-accent);
|
|
437
|
+
background-color: var(--purple-dim);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.badge.budget {
|
|
441
|
+
border-color: var(--obsidian-600);
|
|
442
|
+
color: var(--amber-accent);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
.badge.elapsed {
|
|
446
|
+
border-color: var(--obsidian-600);
|
|
447
|
+
color: var(--slate-400);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
.badge.model {
|
|
451
|
+
border-color: var(--obsidian-600);
|
|
452
|
+
color: var(--slate-300);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.badge.branch {
|
|
456
|
+
border-color: var(--obsidian-700);
|
|
457
|
+
color: var(--slate-400);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/* Footer */
|
|
461
|
+
.rig-footer {
|
|
462
|
+
border-top: 1px solid var(--obsidian-700);
|
|
463
|
+
background-color: var(--obsidian-950);
|
|
464
|
+
font-size: 11px;
|
|
465
|
+
padding: 1.25rem 0;
|
|
466
|
+
margin-top: auto;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
.footer-inner {
|
|
470
|
+
max-width: 1600px;
|
|
471
|
+
margin: 0 auto;
|
|
472
|
+
padding: 0 1.5rem;
|
|
473
|
+
display: flex;
|
|
474
|
+
flex-wrap: wrap;
|
|
475
|
+
align-items: center;
|
|
476
|
+
justify-content: space-between;
|
|
477
|
+
gap: 0.75rem;
|
|
478
|
+
color: var(--slate-500);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.footer-left {
|
|
482
|
+
display: flex;
|
|
483
|
+
flex-wrap: wrap;
|
|
484
|
+
align-items: center;
|
|
485
|
+
gap: 0.5rem;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
.accent-diamond {
|
|
489
|
+
color: var(--amber-glow);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
.footer-right {
|
|
493
|
+
font-size: 10px;
|
|
494
|
+
letter-spacing: 0.05em;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
@media (max-width: 1200px) {
|
|
498
|
+
.columns {
|
|
499
|
+
grid-template-columns: repeat(4, minmax(140px, 1fr));
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
@media (max-width: 768px) {
|
|
504
|
+
.columns {
|
|
505
|
+
grid-template-columns: repeat(2, minmax(130px, 1fr));
|
|
506
|
+
}
|
|
74
507
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"hooks": [
|
|
7
7
|
{
|
|
8
8
|
"type": "command",
|
|
9
|
-
"command": "python3 \"
|
|
9
|
+
"command": "python3 \"skills/gantry/scripts/guard.py\" PreToolUse --json"
|
|
10
10
|
}
|
|
11
11
|
]
|
|
12
12
|
}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"hooks": [
|
|
18
18
|
{
|
|
19
19
|
"type": "command",
|
|
20
|
-
"command": "python3 \"
|
|
20
|
+
"command": "python3 \"skills/gantry/scripts/guard.py\" PostToolUse --json"
|
|
21
21
|
}
|
|
22
22
|
]
|
|
23
23
|
}
|
|
@@ -569,8 +569,14 @@ def main() -> int:
|
|
|
569
569
|
|
|
570
570
|
payload = read_payload()
|
|
571
571
|
cwd = Path(args.cwd).resolve()
|
|
572
|
+
name = tool_name(payload) if payload else ""
|
|
573
|
+
normalized_name = re.sub(r"[^a-z]", "", name)
|
|
572
574
|
|
|
573
575
|
def allow() -> int:
|
|
576
|
+
if args.event == "PostToolUse" and (not args.run_id or normalized_name != "invokesubagent"):
|
|
577
|
+
if args.json:
|
|
578
|
+
print("{}")
|
|
579
|
+
return 0
|
|
574
580
|
if args.json:
|
|
575
581
|
print(json.dumps({"decision": "allow"}, separators=(",", ":")))
|
|
576
582
|
else:
|
|
@@ -119,7 +119,7 @@ def main() -> None:
|
|
|
119
119
|
if ag_hook_frag_path.exists():
|
|
120
120
|
ag_hook_frag = json.loads(ag_hook_frag_path.read_text(encoding="utf-8"))
|
|
121
121
|
else:
|
|
122
|
-
guard_cmd = 'python3 "
|
|
122
|
+
guard_cmd = 'python3 "skills/gantry/scripts/guard.py" PreToolUse --json'
|
|
123
123
|
ag_hook_frag = {
|
|
124
124
|
"hooks": {
|
|
125
125
|
"PreToolUse": [
|