@stevenvincentone/intidev-agentloops 0.1.0
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/CHANGELOG.md +31 -0
- package/LICENSE +21 -0
- package/README.md +221 -0
- package/agentloop.config.json.example +31 -0
- package/dist/aliases.d.ts +25 -0
- package/dist/aliases.js +42 -0
- package/dist/backend.d.ts +29 -0
- package/dist/backend.js +40 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +497 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.js +81 -0
- package/dist/convergence.d.ts +56 -0
- package/dist/convergence.js +64 -0
- package/dist/dashboard.d.ts +23 -0
- package/dist/dashboard.js +234 -0
- package/dist/guards.d.ts +53 -0
- package/dist/guards.js +87 -0
- package/dist/handoff.d.ts +10 -0
- package/dist/handoff.js +23 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +80 -0
- package/dist/knowledge.d.ts +101 -0
- package/dist/knowledge.js +155 -0
- package/dist/mcp.d.ts +130 -0
- package/dist/mcp.js +442 -0
- package/dist/postgres.d.ts +104 -0
- package/dist/postgres.js +364 -0
- package/dist/prior-art.d.ts +65 -0
- package/dist/prior-art.js +114 -0
- package/dist/redaction.d.ts +14 -0
- package/dist/redaction.js +57 -0
- package/dist/serve.d.ts +8 -0
- package/dist/serve.js +42 -0
- package/dist/storage.d.ts +22 -0
- package/dist/storage.js +39 -0
- package/dist/store.d.ts +60 -0
- package/dist/store.js +339 -0
- package/dist/types.d.ts +156 -0
- package/dist/types.js +3 -0
- package/docs/architecture.md +30 -0
- package/docs/config.md +88 -0
- package/docs/mcp.md +88 -0
- package/docs/postgres.md +84 -0
- package/package.json +88 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_MIN_SOURCES = exports.SOURCE_CONVERGENCE_SCHEMA_VERSION = void 0;
|
|
4
|
+
exports.sourceConvergenceReport = sourceConvergenceReport;
|
|
5
|
+
exports.SOURCE_CONVERGENCE_SCHEMA_VERSION = 1;
|
|
6
|
+
exports.DEFAULT_MIN_SOURCES = 2;
|
|
7
|
+
/**
|
|
8
|
+
* Source-convergence audit (ported concept from Inti's ledger): surface the
|
|
9
|
+
* Patterns whose member tickets were reported through **multiple distinct
|
|
10
|
+
* sources**. A pattern corroborated by, say, a smoke run + a user report + an
|
|
11
|
+
* agent is far higher-signal than a single-source cluster.
|
|
12
|
+
*
|
|
13
|
+
* Pure and deterministic apart from `generatedAt`. Patterns are returned sorted
|
|
14
|
+
* by source diversity (then ticket count, then id) so output is stable.
|
|
15
|
+
*/
|
|
16
|
+
function sourceConvergenceReport(tickets, patterns, options = {}) {
|
|
17
|
+
const minSources = Math.max(1, Math.trunc(options.minSources ?? exports.DEFAULT_MIN_SOURCES));
|
|
18
|
+
const familyFilter = options.family;
|
|
19
|
+
const byId = new Map(tickets.map((ticket) => [ticket.id, ticket]));
|
|
20
|
+
const scoped = patterns.filter((pattern) => !familyFilter || pattern.family === familyFilter);
|
|
21
|
+
const analyzed = scoped.map((pattern) => {
|
|
22
|
+
const members = pattern.ticketIds
|
|
23
|
+
.map((id) => byId.get(id))
|
|
24
|
+
.filter((ticket) => Boolean(ticket));
|
|
25
|
+
const sources = {};
|
|
26
|
+
for (const ticket of members) {
|
|
27
|
+
sources[ticket.source] = (sources[ticket.source] ?? 0) + 1;
|
|
28
|
+
}
|
|
29
|
+
const sourceCount = Object.keys(sources).length;
|
|
30
|
+
return {
|
|
31
|
+
id: pattern.id,
|
|
32
|
+
family: pattern.family,
|
|
33
|
+
title: pattern.title,
|
|
34
|
+
status: pattern.status,
|
|
35
|
+
ticketCount: members.length,
|
|
36
|
+
sourceCount,
|
|
37
|
+
sources,
|
|
38
|
+
converged: sourceCount >= minSources,
|
|
39
|
+
tickets: members.map((ticket) => ({
|
|
40
|
+
id: ticket.id,
|
|
41
|
+
alias: ticket.aliases[0] ?? ticket.id,
|
|
42
|
+
source: ticket.source,
|
|
43
|
+
kind: ticket.kind,
|
|
44
|
+
status: ticket.status,
|
|
45
|
+
})),
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
const converged = analyzed.filter((pattern) => pattern.converged);
|
|
49
|
+
const visible = (options.includeAll ? analyzed : converged).sort((a, b) => b.sourceCount - a.sourceCount ||
|
|
50
|
+
b.ticketCount - a.ticketCount ||
|
|
51
|
+
a.id.localeCompare(b.id));
|
|
52
|
+
return {
|
|
53
|
+
schemaVersion: exports.SOURCE_CONVERGENCE_SCHEMA_VERSION,
|
|
54
|
+
generatedAt: new Date().toISOString(),
|
|
55
|
+
filters: { family: familyFilter ?? null, minSources },
|
|
56
|
+
summary: {
|
|
57
|
+
totalPatterns: analyzed.length,
|
|
58
|
+
convergedPatterns: converged.length,
|
|
59
|
+
maxSourceConvergence: analyzed.reduce((max, pattern) => Math.max(max, pattern.sourceCount), 0),
|
|
60
|
+
},
|
|
61
|
+
patterns: visible,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=convergence.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { AgentLoopStore } from "./store";
|
|
2
|
+
import { Pattern, Ticket } from "./types";
|
|
3
|
+
import { SourceConvergenceReport } from "./convergence";
|
|
4
|
+
import { GuardGapReport } from "./guards";
|
|
5
|
+
export interface DashboardData {
|
|
6
|
+
project: string;
|
|
7
|
+
generatedAt: string;
|
|
8
|
+
summary: Awaited<ReturnType<AgentLoopStore["summary"]>>;
|
|
9
|
+
tickets: Ticket[];
|
|
10
|
+
patterns: Pattern[];
|
|
11
|
+
convergence: SourceConvergenceReport;
|
|
12
|
+
guardGaps: GuardGapReport;
|
|
13
|
+
}
|
|
14
|
+
/** Gather everything the dashboard renders from a store (filesystem or Postgres). */
|
|
15
|
+
export declare function gatherDashboardData(store: AgentLoopStore): Promise<DashboardData>;
|
|
16
|
+
export declare function escapeHtml(value: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Render a complete, dependency-free HTML dashboard for the ledger. All dynamic
|
|
19
|
+
* text is HTML-escaped. The output is a single self-contained document (inline
|
|
20
|
+
* CSS + a few lines of tab-switching JS), so it opens directly in a browser or
|
|
21
|
+
* is served as-is.
|
|
22
|
+
*/
|
|
23
|
+
export declare function renderDashboard(data: DashboardData): string;
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.gatherDashboardData = gatherDashboardData;
|
|
4
|
+
exports.escapeHtml = escapeHtml;
|
|
5
|
+
exports.renderDashboard = renderDashboard;
|
|
6
|
+
/** Gather everything the dashboard renders from a store (filesystem or Postgres). */
|
|
7
|
+
async function gatherDashboardData(store) {
|
|
8
|
+
const summary = await store.summary();
|
|
9
|
+
return {
|
|
10
|
+
project: summary.project,
|
|
11
|
+
generatedAt: new Date().toISOString(),
|
|
12
|
+
summary,
|
|
13
|
+
tickets: await store.listTickets({ status: "all" }),
|
|
14
|
+
patterns: await store.listPatterns({ status: "all" }),
|
|
15
|
+
convergence: await store.sourceConvergence({ includeAll: true }),
|
|
16
|
+
guardGaps: await store.guardGaps({}),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const ESCAPES = {
|
|
20
|
+
"&": "&",
|
|
21
|
+
"<": "<",
|
|
22
|
+
">": ">",
|
|
23
|
+
'"': """,
|
|
24
|
+
"'": "'",
|
|
25
|
+
};
|
|
26
|
+
function escapeHtml(value) {
|
|
27
|
+
return value.replace(/[&<>"']/g, (char) => ESCAPES[char]);
|
|
28
|
+
}
|
|
29
|
+
const QUEUE_LABELS = {
|
|
30
|
+
ISSUE: "Issues",
|
|
31
|
+
USER: "User",
|
|
32
|
+
DEV: "Development",
|
|
33
|
+
};
|
|
34
|
+
const QUEUE_ORDER = ["ISSUE", "USER", "DEV"];
|
|
35
|
+
function prefixOf(ticket) {
|
|
36
|
+
const alias = ticket.aliases[0] ?? ticket.id;
|
|
37
|
+
return alias.split("-")[0];
|
|
38
|
+
}
|
|
39
|
+
function card(label, value) {
|
|
40
|
+
return `<div class="card"><div class="num">${escapeHtml(String(value))}</div><div class="lbl">${escapeHtml(label)}</div></div>`;
|
|
41
|
+
}
|
|
42
|
+
function statusBadge(status) {
|
|
43
|
+
return `<span class="badge s-${escapeHtml(status)}">${escapeHtml(status)}</span>`;
|
|
44
|
+
}
|
|
45
|
+
function ticketTable(tickets) {
|
|
46
|
+
if (tickets.length === 0)
|
|
47
|
+
return `<p class="empty">No tickets.</p>`;
|
|
48
|
+
const rows = tickets
|
|
49
|
+
.map((t) => `<tr>
|
|
50
|
+
<td class="mono">${escapeHtml(t.aliases[0] ?? t.id)}</td>
|
|
51
|
+
<td>${escapeHtml(t.kind)}</td>
|
|
52
|
+
<td>${statusBadge(t.status)}</td>
|
|
53
|
+
<td>${escapeHtml(t.family)}</td>
|
|
54
|
+
<td>${escapeHtml(t.source)}</td>
|
|
55
|
+
<td>${escapeHtml(t.title || "(untitled)")}</td>
|
|
56
|
+
</tr>`)
|
|
57
|
+
.join("\n");
|
|
58
|
+
return `<table>
|
|
59
|
+
<thead><tr><th>Alias</th><th>Kind</th><th>Status</th><th>Family</th><th>Source</th><th>Title</th></tr></thead>
|
|
60
|
+
<tbody>${rows}</tbody>
|
|
61
|
+
</table>`;
|
|
62
|
+
}
|
|
63
|
+
function ticketsTab(tickets) {
|
|
64
|
+
const groups = new Map();
|
|
65
|
+
for (const ticket of tickets) {
|
|
66
|
+
const prefix = prefixOf(ticket);
|
|
67
|
+
const list = groups.get(prefix) ?? [];
|
|
68
|
+
list.push(ticket);
|
|
69
|
+
groups.set(prefix, list);
|
|
70
|
+
}
|
|
71
|
+
const orderedPrefixes = [
|
|
72
|
+
...QUEUE_ORDER.filter((p) => groups.has(p)),
|
|
73
|
+
...[...groups.keys()].filter((p) => !QUEUE_ORDER.includes(p)).sort(),
|
|
74
|
+
];
|
|
75
|
+
if (orderedPrefixes.length === 0)
|
|
76
|
+
return `<p class="empty">No tickets yet.</p>`;
|
|
77
|
+
return orderedPrefixes
|
|
78
|
+
.map((prefix) => {
|
|
79
|
+
const list = groups.get(prefix) ?? [];
|
|
80
|
+
const label = QUEUE_LABELS[prefix] ?? prefix;
|
|
81
|
+
return `<h3>${escapeHtml(label)} <span class="count">${list.length}</span></h3>${ticketTable(list)}`;
|
|
82
|
+
})
|
|
83
|
+
.join("\n");
|
|
84
|
+
}
|
|
85
|
+
function patternsTab(patterns) {
|
|
86
|
+
if (patterns.length === 0)
|
|
87
|
+
return `<p class="empty">No patterns yet.</p>`;
|
|
88
|
+
const rows = patterns
|
|
89
|
+
.map((p) => `<tr>
|
|
90
|
+
<td class="mono">${escapeHtml(p.id)}</td>
|
|
91
|
+
<td>${escapeHtml(p.family)}</td>
|
|
92
|
+
<td>${statusBadge(p.status)}</td>
|
|
93
|
+
<td>${p.ticketIds.length}</td>
|
|
94
|
+
<td>${escapeHtml(p.title)}</td>
|
|
95
|
+
</tr>`)
|
|
96
|
+
.join("\n");
|
|
97
|
+
return `<table>
|
|
98
|
+
<thead><tr><th>Pattern</th><th>Family</th><th>Status</th><th>Tickets</th><th>Title</th></tr></thead>
|
|
99
|
+
<tbody>${rows}</tbody>
|
|
100
|
+
</table>`;
|
|
101
|
+
}
|
|
102
|
+
function convergenceTab(report) {
|
|
103
|
+
const head = `<p class="note">${report.summary.convergedPatterns} of ${report.summary.totalPatterns} patterns span multiple sources (max ${report.summary.maxSourceConvergence}).</p>`;
|
|
104
|
+
if (report.patterns.length === 0)
|
|
105
|
+
return `${head}<p class="empty">No patterns.</p>`;
|
|
106
|
+
const rows = report.patterns
|
|
107
|
+
.map((p) => {
|
|
108
|
+
const sources = Object.entries(p.sources)
|
|
109
|
+
.map(([s, n]) => `${escapeHtml(s)}×${n}`)
|
|
110
|
+
.join(", ");
|
|
111
|
+
return `<tr>
|
|
112
|
+
<td class="mono">${escapeHtml(p.id)}</td>
|
|
113
|
+
<td>${escapeHtml(p.family)}</td>
|
|
114
|
+
<td>${p.sourceCount}</td>
|
|
115
|
+
<td>${p.ticketCount}</td>
|
|
116
|
+
<td>${p.converged ? "✓" : ""}</td>
|
|
117
|
+
<td>${sources}</td>
|
|
118
|
+
</tr>`;
|
|
119
|
+
})
|
|
120
|
+
.join("\n");
|
|
121
|
+
return `${head}<table>
|
|
122
|
+
<thead><tr><th>Pattern</th><th>Family</th><th>Sources</th><th>Tickets</th><th>Converged</th><th>Breakdown</th></tr></thead>
|
|
123
|
+
<tbody>${rows}</tbody>
|
|
124
|
+
</table>`;
|
|
125
|
+
}
|
|
126
|
+
function guardGapsTab(report) {
|
|
127
|
+
const head = `<p class="note">${report.summary.gaps} gap(s): ${report.summary.missing} missing, ${report.summary.deferred} deferred (of ${report.summary.resolvedConsidered} resolved considered).</p>`;
|
|
128
|
+
if (report.gaps.length === 0)
|
|
129
|
+
return `${head}<p class="empty">No guard gaps. 🎉</p>`;
|
|
130
|
+
const rows = report.gaps
|
|
131
|
+
.map((g) => `<tr>
|
|
132
|
+
<td class="mono">${escapeHtml(g.alias)}</td>
|
|
133
|
+
<td>${escapeHtml(g.kind)}</td>
|
|
134
|
+
<td><span class="badge gap-${escapeHtml(g.reason)}">${escapeHtml(g.reason)}</span></td>
|
|
135
|
+
<td>${escapeHtml(g.family)}</td>
|
|
136
|
+
</tr>`)
|
|
137
|
+
.join("\n");
|
|
138
|
+
return `${head}<table>
|
|
139
|
+
<thead><tr><th>Ticket</th><th>Kind</th><th>Gap</th><th>Family</th></tr></thead>
|
|
140
|
+
<tbody>${rows}</tbody>
|
|
141
|
+
</table>`;
|
|
142
|
+
}
|
|
143
|
+
const STYLE = `
|
|
144
|
+
:root { --bg:#0f1117; --panel:#171a21; --line:#272b35; --txt:#e6e8ee; --muted:#9aa3b2; --accent:#6ea8fe; }
|
|
145
|
+
* { box-sizing: border-box; }
|
|
146
|
+
body { margin:0; font:14px/1.5 system-ui,-apple-system,Segoe UI,Roboto,sans-serif; background:var(--bg); color:var(--txt); }
|
|
147
|
+
header { padding:24px 28px 8px; }
|
|
148
|
+
h1 { margin:0; font-size:20px; }
|
|
149
|
+
.meta { color:var(--muted); margin:4px 0 0; }
|
|
150
|
+
.cards { display:flex; flex-wrap:wrap; gap:12px; padding:16px 28px; }
|
|
151
|
+
.card { background:var(--panel); border:1px solid var(--line); border-radius:10px; padding:12px 16px; min-width:110px; }
|
|
152
|
+
.card .num { font-size:22px; font-weight:600; }
|
|
153
|
+
.card .lbl { color:var(--muted); font-size:12px; }
|
|
154
|
+
nav { display:flex; gap:4px; padding:0 28px; border-bottom:1px solid var(--line); flex-wrap:wrap; }
|
|
155
|
+
nav button { background:none; border:none; color:var(--muted); padding:10px 14px; cursor:pointer; font-size:14px; border-bottom:2px solid transparent; }
|
|
156
|
+
nav button.active { color:var(--txt); border-bottom-color:var(--accent); }
|
|
157
|
+
main { padding:18px 28px 60px; }
|
|
158
|
+
.tab { display:none; }
|
|
159
|
+
h3 { margin:18px 0 8px; font-size:15px; }
|
|
160
|
+
h3 .count { color:var(--muted); font-weight:400; }
|
|
161
|
+
table { width:100%; border-collapse:collapse; margin-bottom:8px; }
|
|
162
|
+
th, td { text-align:left; padding:7px 10px; border-bottom:1px solid var(--line); vertical-align:top; }
|
|
163
|
+
th { color:var(--muted); font-weight:500; font-size:12px; text-transform:uppercase; letter-spacing:.03em; }
|
|
164
|
+
.mono { font-family:ui-monospace,SFMono-Regular,Menlo,monospace; color:var(--accent); white-space:nowrap; }
|
|
165
|
+
.badge { font-size:12px; padding:1px 8px; border-radius:999px; background:#2a2f3a; }
|
|
166
|
+
.s-resolved { background:#163a2b; color:#7ee2a8; }
|
|
167
|
+
.s-active { background:#16314a; color:#7fb6ff; }
|
|
168
|
+
.s-reopened { background:#4a2516; color:#ffb27f; }
|
|
169
|
+
.s-deferred { background:#33363f; color:#c9ced8; }
|
|
170
|
+
.gap-missing { background:#4a1616; color:#ff8f8f; }
|
|
171
|
+
.gap-deferred { background:#4a3b16; color:#ffd98f; }
|
|
172
|
+
.empty, .note { color:var(--muted); }
|
|
173
|
+
footer { color:var(--muted); padding:0 28px 28px; font-size:12px; }
|
|
174
|
+
`;
|
|
175
|
+
/**
|
|
176
|
+
* Render a complete, dependency-free HTML dashboard for the ledger. All dynamic
|
|
177
|
+
* text is HTML-escaped. The output is a single self-contained document (inline
|
|
178
|
+
* CSS + a few lines of tab-switching JS), so it opens directly in a browser or
|
|
179
|
+
* is served as-is.
|
|
180
|
+
*/
|
|
181
|
+
function renderDashboard(data) {
|
|
182
|
+
const s = data.summary;
|
|
183
|
+
const cards = [
|
|
184
|
+
card("Tickets", s.totalTickets),
|
|
185
|
+
card("Triaged", s.triagedTickets),
|
|
186
|
+
card("Active", s.activeTickets),
|
|
187
|
+
card("Resolved", s.resolvedTickets),
|
|
188
|
+
card("Patterns", data.patterns.length),
|
|
189
|
+
card("Converged", data.convergence.summary.convergedPatterns),
|
|
190
|
+
card("Guard gaps", data.guardGaps.summary.gaps),
|
|
191
|
+
].join("");
|
|
192
|
+
const tabs = [
|
|
193
|
+
{ id: "tickets", label: "Tickets", body: ticketsTab(data.tickets) },
|
|
194
|
+
{ id: "patterns", label: "Patterns", body: patternsTab(data.patterns) },
|
|
195
|
+
{ id: "convergence", label: "Convergence", body: convergenceTab(data.convergence) },
|
|
196
|
+
{ id: "guards", label: "Guard Gaps", body: guardGapsTab(data.guardGaps) },
|
|
197
|
+
];
|
|
198
|
+
const nav = tabs
|
|
199
|
+
.map((t, i) => `<button class="${i === 0 ? "active" : ""}" onclick="showTab('${t.id}', this)">${escapeHtml(t.label)}</button>`)
|
|
200
|
+
.join("");
|
|
201
|
+
const sections = tabs
|
|
202
|
+
.map((t, i) => `<section id="${t.id}" class="tab" style="display:${i === 0 ? "block" : "none"}">${t.body}</section>`)
|
|
203
|
+
.join("\n");
|
|
204
|
+
return `<!DOCTYPE html>
|
|
205
|
+
<html lang="en">
|
|
206
|
+
<head>
|
|
207
|
+
<meta charset="utf-8" />
|
|
208
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
209
|
+
<title>${escapeHtml(data.project)} — AgentLoops</title>
|
|
210
|
+
<style>${STYLE}</style>
|
|
211
|
+
</head>
|
|
212
|
+
<body>
|
|
213
|
+
<header>
|
|
214
|
+
<h1>${escapeHtml(data.project)}</h1>
|
|
215
|
+
<p class="meta">AgentLoops dashboard · generated ${escapeHtml(data.generatedAt)}</p>
|
|
216
|
+
</header>
|
|
217
|
+
<div class="cards">${cards}</div>
|
|
218
|
+
<nav>${nav}</nav>
|
|
219
|
+
<main>
|
|
220
|
+
${sections}
|
|
221
|
+
</main>
|
|
222
|
+
<footer>Read-only snapshot. Manage tickets with the <code>agentloop</code> CLI or MCP server.</footer>
|
|
223
|
+
<script>
|
|
224
|
+
function showTab(id, btn) {
|
|
225
|
+
document.querySelectorAll('.tab').forEach(function (t) { t.style.display = 'none'; });
|
|
226
|
+
document.getElementById(id).style.display = 'block';
|
|
227
|
+
document.querySelectorAll('nav button').forEach(function (b) { b.classList.remove('active'); });
|
|
228
|
+
btn.classList.add('active');
|
|
229
|
+
}
|
|
230
|
+
</script>
|
|
231
|
+
</body>
|
|
232
|
+
</html>`;
|
|
233
|
+
}
|
|
234
|
+
//# sourceMappingURL=dashboard.js.map
|
package/dist/guards.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ProjectConfig, Ticket } from "./types";
|
|
2
|
+
export declare const GUARD_GAP_SCHEMA_VERSION: 1;
|
|
3
|
+
/** Queue prefixes that are expected to carry a regression guard once resolved. */
|
|
4
|
+
export declare const DEFAULT_GUARD_QUEUES: string[];
|
|
5
|
+
export type GuardGapReason = "missing" | "deferred" | "waived";
|
|
6
|
+
export interface GuardGapOptions {
|
|
7
|
+
/** Restrict to a single family. */
|
|
8
|
+
family?: string;
|
|
9
|
+
/** Treat intentionally waived guards as gaps too. Default false. */
|
|
10
|
+
includeWaived?: boolean;
|
|
11
|
+
/** Consider every resolved ticket, not just guard-relevant queues. Default false. */
|
|
12
|
+
allKinds?: boolean;
|
|
13
|
+
/** Queue prefixes that expect a guard. Default ISSUE, USER. */
|
|
14
|
+
guardQueues?: string[];
|
|
15
|
+
}
|
|
16
|
+
export interface GuardGap {
|
|
17
|
+
id: string;
|
|
18
|
+
alias: string;
|
|
19
|
+
kind: string;
|
|
20
|
+
source: string;
|
|
21
|
+
family: string;
|
|
22
|
+
guardStatus: string;
|
|
23
|
+
reason: GuardGapReason;
|
|
24
|
+
resolutionSummary?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface GuardGapReport {
|
|
27
|
+
schemaVersion: typeof GUARD_GAP_SCHEMA_VERSION;
|
|
28
|
+
generatedAt: string;
|
|
29
|
+
filters: {
|
|
30
|
+
family: string | null;
|
|
31
|
+
includeWaived: boolean;
|
|
32
|
+
allKinds: boolean;
|
|
33
|
+
};
|
|
34
|
+
summary: {
|
|
35
|
+
/** Resolved tickets considered (after queue/family scoping). */
|
|
36
|
+
resolvedConsidered: number;
|
|
37
|
+
guarded: number;
|
|
38
|
+
gaps: number;
|
|
39
|
+
missing: number;
|
|
40
|
+
deferred: number;
|
|
41
|
+
waived: number;
|
|
42
|
+
};
|
|
43
|
+
gaps: GuardGap[];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Guard-gap report (ported concept from Inti's guard audit): resolved tickets
|
|
47
|
+
* that should carry a regression guard but do not. By default only tickets in
|
|
48
|
+
* guard-relevant queues (ISSUE, USER — defects and user reports) are considered;
|
|
49
|
+
* `allKinds` widens it to every resolved ticket.
|
|
50
|
+
*
|
|
51
|
+
* Pure and deterministic apart from `generatedAt`.
|
|
52
|
+
*/
|
|
53
|
+
export declare function guardGapReport(tickets: Ticket[], config: ProjectConfig, options?: GuardGapOptions): GuardGapReport;
|
package/dist/guards.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_GUARD_QUEUES = exports.GUARD_GAP_SCHEMA_VERSION = void 0;
|
|
4
|
+
exports.guardGapReport = guardGapReport;
|
|
5
|
+
const aliases_1 = require("./aliases");
|
|
6
|
+
exports.GUARD_GAP_SCHEMA_VERSION = 1;
|
|
7
|
+
/** Guard statuses that count as an active regression guard. */
|
|
8
|
+
const ACTIVE_GUARD = new Set(["guard_added", "guard_existing"]);
|
|
9
|
+
/** Queue prefixes that are expected to carry a regression guard once resolved. */
|
|
10
|
+
exports.DEFAULT_GUARD_QUEUES = ["ISSUE", "USER"];
|
|
11
|
+
const REASON_ORDER = { missing: 0, deferred: 1, waived: 2 };
|
|
12
|
+
/**
|
|
13
|
+
* Guard-gap report (ported concept from Inti's guard audit): resolved tickets
|
|
14
|
+
* that should carry a regression guard but do not. By default only tickets in
|
|
15
|
+
* guard-relevant queues (ISSUE, USER — defects and user reports) are considered;
|
|
16
|
+
* `allKinds` widens it to every resolved ticket.
|
|
17
|
+
*
|
|
18
|
+
* Pure and deterministic apart from `generatedAt`.
|
|
19
|
+
*/
|
|
20
|
+
function guardGapReport(tickets, config, options = {}) {
|
|
21
|
+
const family = options.family;
|
|
22
|
+
const includeWaived = options.includeWaived ?? false;
|
|
23
|
+
const allKinds = options.allKinds ?? false;
|
|
24
|
+
const guardQueues = new Set((options.guardQueues ?? exports.DEFAULT_GUARD_QUEUES).map((prefix) => prefix.toUpperCase()));
|
|
25
|
+
const relevant = tickets.filter((ticket) => {
|
|
26
|
+
if (ticket.status !== "resolved")
|
|
27
|
+
return false;
|
|
28
|
+
if (family && ticket.family !== family)
|
|
29
|
+
return false;
|
|
30
|
+
if (allKinds)
|
|
31
|
+
return true;
|
|
32
|
+
return guardQueues.has((0, aliases_1.resolveQueuePrefix)({ kind: ticket.kind, source: ticket.source }, config));
|
|
33
|
+
});
|
|
34
|
+
let guarded = 0;
|
|
35
|
+
let missing = 0;
|
|
36
|
+
let deferred = 0;
|
|
37
|
+
let waived = 0;
|
|
38
|
+
const gaps = [];
|
|
39
|
+
for (const ticket of relevant) {
|
|
40
|
+
const guardStatus = ticket.guardStatus ?? "none";
|
|
41
|
+
if (ACTIVE_GUARD.has(guardStatus)) {
|
|
42
|
+
guarded += 1;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
let reason;
|
|
46
|
+
if (guardStatus === "guard_deferred") {
|
|
47
|
+
reason = "deferred";
|
|
48
|
+
deferred += 1;
|
|
49
|
+
}
|
|
50
|
+
else if (guardStatus === "guard_waived") {
|
|
51
|
+
reason = "waived";
|
|
52
|
+
waived += 1;
|
|
53
|
+
if (!includeWaived)
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
reason = "missing";
|
|
58
|
+
missing += 1;
|
|
59
|
+
}
|
|
60
|
+
gaps.push({
|
|
61
|
+
id: ticket.id,
|
|
62
|
+
alias: ticket.aliases[0] ?? ticket.id,
|
|
63
|
+
kind: ticket.kind,
|
|
64
|
+
source: ticket.source,
|
|
65
|
+
family: ticket.family,
|
|
66
|
+
guardStatus,
|
|
67
|
+
reason,
|
|
68
|
+
resolutionSummary: ticket.resolutionSummary,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
gaps.sort((a, b) => REASON_ORDER[a.reason] - REASON_ORDER[b.reason] || a.id.localeCompare(b.id));
|
|
72
|
+
return {
|
|
73
|
+
schemaVersion: exports.GUARD_GAP_SCHEMA_VERSION,
|
|
74
|
+
generatedAt: new Date().toISOString(),
|
|
75
|
+
filters: { family: family ?? null, includeWaived, allKinds },
|
|
76
|
+
summary: {
|
|
77
|
+
resolvedConsidered: relevant.length,
|
|
78
|
+
guarded,
|
|
79
|
+
gaps: gaps.length,
|
|
80
|
+
missing,
|
|
81
|
+
deferred,
|
|
82
|
+
waived,
|
|
83
|
+
},
|
|
84
|
+
gaps,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=guards.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Ticket } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Build the copyable agent handoff prompt for a ticket.
|
|
4
|
+
*
|
|
5
|
+
* Pure and deterministic: if the ticket carries an explicit `handoffText`,
|
|
6
|
+
* that is used verbatim; otherwise a structured default is derived from the
|
|
7
|
+
* ticket fields. Shared by the CLI `handoff` command and the MCP
|
|
8
|
+
* `agentloop_handoff` tool so both stay in sync.
|
|
9
|
+
*/
|
|
10
|
+
export declare function buildHandoffPrompt(ticket: Ticket): string;
|
package/dist/handoff.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildHandoffPrompt = buildHandoffPrompt;
|
|
4
|
+
/**
|
|
5
|
+
* Build the copyable agent handoff prompt for a ticket.
|
|
6
|
+
*
|
|
7
|
+
* Pure and deterministic: if the ticket carries an explicit `handoffText`,
|
|
8
|
+
* that is used verbatim; otherwise a structured default is derived from the
|
|
9
|
+
* ticket fields. Shared by the CLI `handoff` command and the MCP
|
|
10
|
+
* `agentloop_handoff` tool so both stay in sync.
|
|
11
|
+
*/
|
|
12
|
+
function buildHandoffPrompt(ticket) {
|
|
13
|
+
if (ticket.handoffText) {
|
|
14
|
+
return ticket.handoffText;
|
|
15
|
+
}
|
|
16
|
+
return [
|
|
17
|
+
`Fix ${ticket.kind} ${ticket.id}: ${ticket.title}`,
|
|
18
|
+
`Symptom: ${ticket.summary}`,
|
|
19
|
+
`Family: ${ticket.family}`,
|
|
20
|
+
`Severity: ${ticket.severity}`,
|
|
21
|
+
].join("\n");
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=handoff.js.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export { AgentLoopStore } from "./store";
|
|
2
|
+
export * from "./types";
|
|
3
|
+
export * from "./config";
|
|
4
|
+
export { FilesystemStateBackend, MemoryStateBackend, } from "./backend";
|
|
5
|
+
export type { StateBackend } from "./backend";
|
|
6
|
+
export { PostgresStateBackend, TICKET_SCHEMA_SQL, serializeState, deserializeRows, } from "./postgres";
|
|
7
|
+
export type { PgClient, RelationalRows } from "./postgres";
|
|
8
|
+
export { resolveBackend, resolvePostgresUrl } from "./storage";
|
|
9
|
+
export type { BackendSelection, ResolveBackendOptions } from "./storage";
|
|
10
|
+
export { renderDashboard, gatherDashboardData, escapeHtml } from "./dashboard";
|
|
11
|
+
export type { DashboardData } from "./dashboard";
|
|
12
|
+
export { createDashboardServer } from "./serve";
|
|
13
|
+
export { buildHandoffPrompt } from "./handoff";
|
|
14
|
+
export { noopRedactor, createPatternRedactor, resolveRedactor } from "./redaction";
|
|
15
|
+
export { deriveAliases, resolveQueuePrefix, canonicalKey, padSeq } from "./aliases";
|
|
16
|
+
export { sourceConvergenceReport, SOURCE_CONVERGENCE_SCHEMA_VERSION, DEFAULT_MIN_SOURCES, } from "./convergence";
|
|
17
|
+
export type { SourceConvergenceOptions, SourceConvergenceReport, ConvergencePattern, ConvergenceTicketRef, } from "./convergence";
|
|
18
|
+
export { guardGapReport, GUARD_GAP_SCHEMA_VERSION, DEFAULT_GUARD_QUEUES } from "./guards";
|
|
19
|
+
export type { GuardGapOptions, GuardGapReport, GuardGap, GuardGapReason, } from "./guards";
|
|
20
|
+
export { resolutionKnowledge, knowledgeGaps, KNOWLEDGE_SCHEMA_VERSION } from "./knowledge";
|
|
21
|
+
export type { KnowledgeEntry, KnowledgeSearchOptions, ResolutionKnowledgeReport, KnowledgeGap, KnowledgeGapReason, KnowledgeGapsOptions, KnowledgeGapsReport, } from "./knowledge";
|
|
22
|
+
export { relatedTickets, PRIOR_ART_SCHEMA_VERSION, DEFAULT_PRIOR_ART_WEIGHTS, } from "./prior-art";
|
|
23
|
+
export type { PriorArtWeights, PriorArtOptions, PriorArtReport, RelatedTicket, } from "./prior-art";
|
|
24
|
+
export { createMcpServer, startStdioMcpServer, summaryTool, listTool, showTool, handoffTool, createTicketTool, noteTool, workflowTool, resolveTool, guardTool, MCP_SCHEMA_VERSION, MCP_SERVER_NAME, } from "./mcp";
|
|
25
|
+
export type { CreateMcpServerOptions, WriteAction, WriteResult } from "./mcp";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.MCP_SERVER_NAME = exports.MCP_SCHEMA_VERSION = exports.guardTool = exports.resolveTool = exports.workflowTool = exports.noteTool = exports.createTicketTool = exports.handoffTool = exports.showTool = exports.listTool = exports.summaryTool = exports.startStdioMcpServer = exports.createMcpServer = exports.DEFAULT_PRIOR_ART_WEIGHTS = exports.PRIOR_ART_SCHEMA_VERSION = exports.relatedTickets = exports.KNOWLEDGE_SCHEMA_VERSION = exports.knowledgeGaps = exports.resolutionKnowledge = exports.DEFAULT_GUARD_QUEUES = exports.GUARD_GAP_SCHEMA_VERSION = exports.guardGapReport = exports.DEFAULT_MIN_SOURCES = exports.SOURCE_CONVERGENCE_SCHEMA_VERSION = exports.sourceConvergenceReport = exports.padSeq = exports.canonicalKey = exports.resolveQueuePrefix = exports.deriveAliases = exports.resolveRedactor = exports.createPatternRedactor = exports.noopRedactor = exports.buildHandoffPrompt = exports.createDashboardServer = exports.escapeHtml = exports.gatherDashboardData = exports.renderDashboard = exports.resolvePostgresUrl = exports.resolveBackend = exports.deserializeRows = exports.serializeState = exports.TICKET_SCHEMA_SQL = exports.PostgresStateBackend = exports.MemoryStateBackend = exports.FilesystemStateBackend = exports.AgentLoopStore = void 0;
|
|
18
|
+
var store_1 = require("./store");
|
|
19
|
+
Object.defineProperty(exports, "AgentLoopStore", { enumerable: true, get: function () { return store_1.AgentLoopStore; } });
|
|
20
|
+
__exportStar(require("./types"), exports);
|
|
21
|
+
__exportStar(require("./config"), exports);
|
|
22
|
+
var backend_1 = require("./backend");
|
|
23
|
+
Object.defineProperty(exports, "FilesystemStateBackend", { enumerable: true, get: function () { return backend_1.FilesystemStateBackend; } });
|
|
24
|
+
Object.defineProperty(exports, "MemoryStateBackend", { enumerable: true, get: function () { return backend_1.MemoryStateBackend; } });
|
|
25
|
+
var postgres_1 = require("./postgres");
|
|
26
|
+
Object.defineProperty(exports, "PostgresStateBackend", { enumerable: true, get: function () { return postgres_1.PostgresStateBackend; } });
|
|
27
|
+
Object.defineProperty(exports, "TICKET_SCHEMA_SQL", { enumerable: true, get: function () { return postgres_1.TICKET_SCHEMA_SQL; } });
|
|
28
|
+
Object.defineProperty(exports, "serializeState", { enumerable: true, get: function () { return postgres_1.serializeState; } });
|
|
29
|
+
Object.defineProperty(exports, "deserializeRows", { enumerable: true, get: function () { return postgres_1.deserializeRows; } });
|
|
30
|
+
var storage_1 = require("./storage");
|
|
31
|
+
Object.defineProperty(exports, "resolveBackend", { enumerable: true, get: function () { return storage_1.resolveBackend; } });
|
|
32
|
+
Object.defineProperty(exports, "resolvePostgresUrl", { enumerable: true, get: function () { return storage_1.resolvePostgresUrl; } });
|
|
33
|
+
var dashboard_1 = require("./dashboard");
|
|
34
|
+
Object.defineProperty(exports, "renderDashboard", { enumerable: true, get: function () { return dashboard_1.renderDashboard; } });
|
|
35
|
+
Object.defineProperty(exports, "gatherDashboardData", { enumerable: true, get: function () { return dashboard_1.gatherDashboardData; } });
|
|
36
|
+
Object.defineProperty(exports, "escapeHtml", { enumerable: true, get: function () { return dashboard_1.escapeHtml; } });
|
|
37
|
+
var serve_1 = require("./serve");
|
|
38
|
+
Object.defineProperty(exports, "createDashboardServer", { enumerable: true, get: function () { return serve_1.createDashboardServer; } });
|
|
39
|
+
var handoff_1 = require("./handoff");
|
|
40
|
+
Object.defineProperty(exports, "buildHandoffPrompt", { enumerable: true, get: function () { return handoff_1.buildHandoffPrompt; } });
|
|
41
|
+
var redaction_1 = require("./redaction");
|
|
42
|
+
Object.defineProperty(exports, "noopRedactor", { enumerable: true, get: function () { return redaction_1.noopRedactor; } });
|
|
43
|
+
Object.defineProperty(exports, "createPatternRedactor", { enumerable: true, get: function () { return redaction_1.createPatternRedactor; } });
|
|
44
|
+
Object.defineProperty(exports, "resolveRedactor", { enumerable: true, get: function () { return redaction_1.resolveRedactor; } });
|
|
45
|
+
var aliases_1 = require("./aliases");
|
|
46
|
+
Object.defineProperty(exports, "deriveAliases", { enumerable: true, get: function () { return aliases_1.deriveAliases; } });
|
|
47
|
+
Object.defineProperty(exports, "resolveQueuePrefix", { enumerable: true, get: function () { return aliases_1.resolveQueuePrefix; } });
|
|
48
|
+
Object.defineProperty(exports, "canonicalKey", { enumerable: true, get: function () { return aliases_1.canonicalKey; } });
|
|
49
|
+
Object.defineProperty(exports, "padSeq", { enumerable: true, get: function () { return aliases_1.padSeq; } });
|
|
50
|
+
var convergence_1 = require("./convergence");
|
|
51
|
+
Object.defineProperty(exports, "sourceConvergenceReport", { enumerable: true, get: function () { return convergence_1.sourceConvergenceReport; } });
|
|
52
|
+
Object.defineProperty(exports, "SOURCE_CONVERGENCE_SCHEMA_VERSION", { enumerable: true, get: function () { return convergence_1.SOURCE_CONVERGENCE_SCHEMA_VERSION; } });
|
|
53
|
+
Object.defineProperty(exports, "DEFAULT_MIN_SOURCES", { enumerable: true, get: function () { return convergence_1.DEFAULT_MIN_SOURCES; } });
|
|
54
|
+
var guards_1 = require("./guards");
|
|
55
|
+
Object.defineProperty(exports, "guardGapReport", { enumerable: true, get: function () { return guards_1.guardGapReport; } });
|
|
56
|
+
Object.defineProperty(exports, "GUARD_GAP_SCHEMA_VERSION", { enumerable: true, get: function () { return guards_1.GUARD_GAP_SCHEMA_VERSION; } });
|
|
57
|
+
Object.defineProperty(exports, "DEFAULT_GUARD_QUEUES", { enumerable: true, get: function () { return guards_1.DEFAULT_GUARD_QUEUES; } });
|
|
58
|
+
var knowledge_1 = require("./knowledge");
|
|
59
|
+
Object.defineProperty(exports, "resolutionKnowledge", { enumerable: true, get: function () { return knowledge_1.resolutionKnowledge; } });
|
|
60
|
+
Object.defineProperty(exports, "knowledgeGaps", { enumerable: true, get: function () { return knowledge_1.knowledgeGaps; } });
|
|
61
|
+
Object.defineProperty(exports, "KNOWLEDGE_SCHEMA_VERSION", { enumerable: true, get: function () { return knowledge_1.KNOWLEDGE_SCHEMA_VERSION; } });
|
|
62
|
+
var prior_art_1 = require("./prior-art");
|
|
63
|
+
Object.defineProperty(exports, "relatedTickets", { enumerable: true, get: function () { return prior_art_1.relatedTickets; } });
|
|
64
|
+
Object.defineProperty(exports, "PRIOR_ART_SCHEMA_VERSION", { enumerable: true, get: function () { return prior_art_1.PRIOR_ART_SCHEMA_VERSION; } });
|
|
65
|
+
Object.defineProperty(exports, "DEFAULT_PRIOR_ART_WEIGHTS", { enumerable: true, get: function () { return prior_art_1.DEFAULT_PRIOR_ART_WEIGHTS; } });
|
|
66
|
+
var mcp_1 = require("./mcp");
|
|
67
|
+
Object.defineProperty(exports, "createMcpServer", { enumerable: true, get: function () { return mcp_1.createMcpServer; } });
|
|
68
|
+
Object.defineProperty(exports, "startStdioMcpServer", { enumerable: true, get: function () { return mcp_1.startStdioMcpServer; } });
|
|
69
|
+
Object.defineProperty(exports, "summaryTool", { enumerable: true, get: function () { return mcp_1.summaryTool; } });
|
|
70
|
+
Object.defineProperty(exports, "listTool", { enumerable: true, get: function () { return mcp_1.listTool; } });
|
|
71
|
+
Object.defineProperty(exports, "showTool", { enumerable: true, get: function () { return mcp_1.showTool; } });
|
|
72
|
+
Object.defineProperty(exports, "handoffTool", { enumerable: true, get: function () { return mcp_1.handoffTool; } });
|
|
73
|
+
Object.defineProperty(exports, "createTicketTool", { enumerable: true, get: function () { return mcp_1.createTicketTool; } });
|
|
74
|
+
Object.defineProperty(exports, "noteTool", { enumerable: true, get: function () { return mcp_1.noteTool; } });
|
|
75
|
+
Object.defineProperty(exports, "workflowTool", { enumerable: true, get: function () { return mcp_1.workflowTool; } });
|
|
76
|
+
Object.defineProperty(exports, "resolveTool", { enumerable: true, get: function () { return mcp_1.resolveTool; } });
|
|
77
|
+
Object.defineProperty(exports, "guardTool", { enumerable: true, get: function () { return mcp_1.guardTool; } });
|
|
78
|
+
Object.defineProperty(exports, "MCP_SCHEMA_VERSION", { enumerable: true, get: function () { return mcp_1.MCP_SCHEMA_VERSION; } });
|
|
79
|
+
Object.defineProperty(exports, "MCP_SERVER_NAME", { enumerable: true, get: function () { return mcp_1.MCP_SERVER_NAME; } });
|
|
80
|
+
//# sourceMappingURL=index.js.map
|