@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.
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolvePostgresUrl = resolvePostgresUrl;
4
+ exports.resolveBackend = resolveBackend;
5
+ const backend_1 = require("./backend");
6
+ const postgres_1 = require("./postgres");
7
+ /** Connection string precedence: explicit arg → `DATABASE_URL` env → config. */
8
+ function resolvePostgresUrl(options) {
9
+ return options.databaseUrl ?? process.env.DATABASE_URL ?? options.config.storage?.databaseUrl;
10
+ }
11
+ /**
12
+ * Pick a `StateBackend` for the CLI/MCP: Postgres when a connection string is
13
+ * configured, otherwise the filesystem. `pg` is loaded lazily and is an optional
14
+ * peer dependency — filesystem users never need it.
15
+ */
16
+ async function resolveBackend(options) {
17
+ const url = resolvePostgresUrl(options);
18
+ if (!url) {
19
+ return {
20
+ backend: new backend_1.FilesystemStateBackend(options.cwd),
21
+ kind: "filesystem",
22
+ dispose: async () => { },
23
+ };
24
+ }
25
+ const { client, end } = await connectPostgres(url);
26
+ return { backend: new postgres_1.PostgresStateBackend(client), kind: "postgres", dispose: end };
27
+ }
28
+ async function connectPostgres(connectionString) {
29
+ let mod;
30
+ try {
31
+ mod = await import("pg");
32
+ }
33
+ catch {
34
+ throw new Error("Postgres backend selected (DATABASE_URL or storage.databaseUrl is set) but the 'pg' package is not installed. Install it with: npm install pg");
35
+ }
36
+ const pool = new mod.Pool({ connectionString });
37
+ return { client: pool, end: () => pool.end() };
38
+ }
39
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1,60 @@
1
+ import { CreateTicketInput, GuardStatus, LoopState, NoteType, Pattern, PatternStatus, ProjectConfig, ResolveInput, Ticket, TicketRedactor, TicketStatus } from "./types";
2
+ import { StateBackend } from "./backend";
3
+ import { SourceConvergenceOptions, SourceConvergenceReport } from "./convergence";
4
+ import { GuardGapOptions, GuardGapReport } from "./guards";
5
+ import { KnowledgeSearchOptions, ResolutionKnowledgeReport, KnowledgeGapsOptions, KnowledgeGapsReport } from "./knowledge";
6
+ import { PriorArtOptions, PriorArtReport } from "./prior-art";
7
+ export declare class AgentLoopStore {
8
+ private readonly config;
9
+ private state;
10
+ private readonly backend;
11
+ private readonly redactor;
12
+ constructor(cwd: string, config: ProjectConfig, options?: {
13
+ redactor?: TicketRedactor;
14
+ backend?: StateBackend;
15
+ });
16
+ private redact;
17
+ ensureInitialized(project?: string): Promise<LoopState>;
18
+ createTicket(input: CreateTicketInput): Promise<Ticket>;
19
+ listTickets(opts: {
20
+ status?: TicketStatus | "all";
21
+ kind?: string;
22
+ }): Promise<Ticket[]>;
23
+ listPatterns(opts: {
24
+ status?: PatternStatus | "all";
25
+ }): Promise<Pattern[]>;
26
+ getTicketByAnyId(rawId: string): Promise<Ticket | undefined>;
27
+ showTicket(rawId: string): Promise<Ticket | undefined>;
28
+ beginTicket(rawId: string): Promise<Ticket>;
29
+ resolveTicket(input: ResolveInput): Promise<Ticket>;
30
+ reopenTicket(rawId: string, reason: string): Promise<Ticket>;
31
+ deferTicket(rawId: string, reason?: string): Promise<Ticket>;
32
+ addTicketNote(rawId: string, type: NoteType, body: string, author?: string): Promise<Ticket>;
33
+ setGuard(rawId: string, status: GuardStatus, summary?: string): Promise<Ticket>;
34
+ private noteCtx;
35
+ resolvePattern(patternId: string, note: string): Promise<Pattern>;
36
+ summary(): Promise<{
37
+ project: string;
38
+ totalTickets: number;
39
+ activeTickets: number;
40
+ triagedTickets: number;
41
+ resolvedTickets: number;
42
+ reopenedTickets: number;
43
+ deferredTickets: number;
44
+ openPatterns: number;
45
+ stalledPatterns: number;
46
+ resolvedPatterns: number;
47
+ }>;
48
+ getPattern(id: string): Promise<Pattern | undefined>;
49
+ getConfig(): ProjectConfig;
50
+ sourceConvergence(options?: SourceConvergenceOptions): Promise<SourceConvergenceReport>;
51
+ guardGaps(options?: GuardGapOptions): Promise<GuardGapReport>;
52
+ searchKnowledge(options?: KnowledgeSearchOptions): Promise<ResolutionKnowledgeReport>;
53
+ knowledgeGaps(options?: KnowledgeGapsOptions): Promise<KnowledgeGapsReport>;
54
+ related(rawId: string, options?: PriorArtOptions): Promise<PriorArtReport>;
55
+ private transitionTicket;
56
+ private attachPattern;
57
+ private persist;
58
+ private get nowState();
59
+ }
60
+ export declare function normalizeTicketInput(raw: string, tickets: Ticket[]): string;
package/dist/store.js ADDED
@@ -0,0 +1,339 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AgentLoopStore = void 0;
4
+ exports.normalizeTicketInput = normalizeTicketInput;
5
+ const config_1 = require("./config");
6
+ const redaction_1 = require("./redaction");
7
+ const backend_1 = require("./backend");
8
+ const aliases_1 = require("./aliases");
9
+ const convergence_1 = require("./convergence");
10
+ const guards_1 = require("./guards");
11
+ const knowledge_1 = require("./knowledge");
12
+ const prior_art_1 = require("./prior-art");
13
+ const SEQ_PAD = 6;
14
+ function ticketId(seq) {
15
+ return `ISSUE-${String(seq).padStart(SEQ_PAD, "0")}`;
16
+ }
17
+ function patternId(seq) {
18
+ return `PATTERN-${String(seq).padStart(SEQ_PAD, "0")}`;
19
+ }
20
+ function nowIso() {
21
+ return new Date().toISOString();
22
+ }
23
+ class AgentLoopStore {
24
+ config;
25
+ state = null;
26
+ backend;
27
+ redactor;
28
+ constructor(cwd, config, options = {}) {
29
+ this.config = config;
30
+ this.backend = options.backend ?? new backend_1.FilesystemStateBackend(cwd);
31
+ this.redactor = (0, redaction_1.resolveRedactor)(config, options.redactor);
32
+ }
33
+ redact(value, context) {
34
+ return this.redactor.redactText(value, context);
35
+ }
36
+ async ensureInitialized(project = this.config.projectName) {
37
+ if (this.state) {
38
+ return this.state;
39
+ }
40
+ const loaded = await this.backend.load();
41
+ if (!loaded) {
42
+ this.state = {
43
+ version: 1,
44
+ project,
45
+ createdAt: nowIso(),
46
+ updatedAt: nowIso(),
47
+ nextTicketSeq: 0,
48
+ nextPatternSeq: 0,
49
+ tickets: [],
50
+ patterns: [],
51
+ };
52
+ await this.persist();
53
+ return this.state;
54
+ }
55
+ this.state = loaded;
56
+ if (!this.state.project) {
57
+ this.state.project = project;
58
+ }
59
+ return this.state;
60
+ }
61
+ async createTicket(input) {
62
+ const state = await this.ensureInitialized();
63
+ const { title, summary, family, kind, source, severity, confidence, tags = [], handoffText } = input;
64
+ const missing = (0, config_1.requiredFields)(this.config, kind).filter((field) => !input[field]);
65
+ if (missing.length > 0) {
66
+ throw new Error(`Missing required fields for ${kind}: ${missing.join(", ")}`);
67
+ }
68
+ const id = ticketId(++state.nextTicketSeq);
69
+ const defaultKindConfig = this.config.ticketKinds.find((entry) => entry.kind === kind);
70
+ const defaults = defaultKindConfig?.defaultSeverity ?? "medium";
71
+ const c = this.nowState;
72
+ const aliases = (0, aliases_1.deriveAliases)({ kind, source }, state.nextTicketSeq, this.config);
73
+ const ctx = (field) => ({ field, ticketKind: kind, source });
74
+ const ticket = {
75
+ id,
76
+ family,
77
+ kind,
78
+ source,
79
+ title: this.redact(title, ctx("title")),
80
+ summary: this.redact(summary, ctx("summary")),
81
+ severity: severity ?? defaults,
82
+ confidence: confidence ?? "medium",
83
+ status: "triaged",
84
+ createdAt: c,
85
+ updatedAt: c,
86
+ aliases: Array.from(new Set(aliases)),
87
+ tags: Array.from(new Set(tags)),
88
+ notes: [],
89
+ handoffText: handoffText ? this.redact(handoffText, ctx("handoffText")) : undefined,
90
+ reproducible: true,
91
+ };
92
+ ticket.patternId = this.attachPattern(state, family, ticket.id);
93
+ state.tickets.push(ticket);
94
+ state.updatedAt = nowIso();
95
+ await this.persist();
96
+ return ticket;
97
+ }
98
+ async listTickets(opts) {
99
+ const state = await this.ensureInitialized();
100
+ let rows = [...state.tickets];
101
+ if (opts.status && opts.status !== "all") {
102
+ rows = rows.filter((ticket) => ticket.status === opts.status);
103
+ }
104
+ if (opts.kind) {
105
+ rows = rows.filter((ticket) => ticket.kind === opts.kind);
106
+ }
107
+ return rows.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
108
+ }
109
+ async listPatterns(opts) {
110
+ const state = await this.ensureInitialized();
111
+ const rows = [...state.patterns];
112
+ if (opts.status && opts.status !== "all") {
113
+ return rows.filter((pattern) => pattern.status === opts.status).sort((a, b) => b.createdAt.localeCompare(a.createdAt));
114
+ }
115
+ return rows.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
116
+ }
117
+ async getTicketByAnyId(rawId) {
118
+ const state = await this.ensureInitialized();
119
+ const normalized = normalizeTicketInput(rawId, state.tickets);
120
+ return state.tickets.find((ticket) => ticket.id === normalized);
121
+ }
122
+ async showTicket(rawId) {
123
+ return this.getTicketByAnyId(rawId);
124
+ }
125
+ async beginTicket(rawId) {
126
+ const ticket = await this.transitionTicket(rawId, "active");
127
+ ticket.startedAt = nowIso();
128
+ await this.persist();
129
+ return ticket;
130
+ }
131
+ async resolveTicket(input) {
132
+ const ticket = await this.transitionTicket(input.id, "resolved");
133
+ const ctx = (field) => ({
134
+ field,
135
+ ticketKind: ticket.kind,
136
+ source: ticket.source,
137
+ });
138
+ ticket.resolutionSummary = this.redact(input.summary, ctx("resolutionSummary"));
139
+ ticket.verification = input.verification
140
+ ? this.redact(input.verification, ctx("verification"))
141
+ : undefined;
142
+ ticket.resolvedAt = nowIso();
143
+ ticket.guardStatus = input.guardStatus ?? "none";
144
+ ticket.guardSummary = input.guardSummary
145
+ ? this.redact(input.guardSummary, ctx("guardSummary"))
146
+ : undefined;
147
+ await this.persist();
148
+ return ticket;
149
+ }
150
+ async reopenTicket(rawId, reason) {
151
+ const ticket = await this.transitionTicket(rawId, "reopened");
152
+ ticket.notes.push({
153
+ id: `${ticket.id}-note-${Date.now()}`,
154
+ type: "hypothesis",
155
+ body: `Reopened: ${this.redact(reason, this.noteCtx(ticket))}`,
156
+ createdAt: nowIso(),
157
+ });
158
+ ticket.updatedAt = nowIso();
159
+ await this.persist();
160
+ return ticket;
161
+ }
162
+ async deferTicket(rawId, reason) {
163
+ const ticket = await this.transitionTicket(rawId, "deferred");
164
+ if (reason) {
165
+ ticket.notes.push({
166
+ id: `${ticket.id}-note-${Date.now()}`,
167
+ type: "triage",
168
+ body: `Deferred: ${this.redact(reason, this.noteCtx(ticket))}`,
169
+ createdAt: nowIso(),
170
+ });
171
+ }
172
+ ticket.updatedAt = nowIso();
173
+ await this.persist();
174
+ return ticket;
175
+ }
176
+ async addTicketNote(rawId, type, body, author) {
177
+ const ticket = await this.transitionTicket(rawId, undefined);
178
+ ticket.notes.push({
179
+ id: `${ticket.id}-note-${Date.now()}`,
180
+ type,
181
+ body: this.redact(body, this.noteCtx(ticket)),
182
+ author,
183
+ createdAt: nowIso(),
184
+ });
185
+ ticket.updatedAt = nowIso();
186
+ await this.persist();
187
+ return ticket;
188
+ }
189
+ async setGuard(rawId, status, summary) {
190
+ const ticket = await this.transitionTicket(rawId, undefined);
191
+ ticket.guardStatus = status;
192
+ ticket.guardSummary = summary
193
+ ? this.redact(summary, { field: "guardSummary", ticketKind: ticket.kind, source: ticket.source })
194
+ : undefined;
195
+ await this.persist();
196
+ return ticket;
197
+ }
198
+ noteCtx(ticket) {
199
+ return { field: "note", ticketKind: ticket.kind, source: ticket.source };
200
+ }
201
+ async resolvePattern(patternId, note) {
202
+ const state = await this.ensureInitialized();
203
+ const pattern = state.patterns.find((entry) => entry.id === patternId);
204
+ if (!pattern) {
205
+ throw new Error(`Pattern not found: ${patternId}`);
206
+ }
207
+ if (pattern.status !== "resolved") {
208
+ pattern.status = "resolved";
209
+ pattern.updatedAt = nowIso();
210
+ if (note) {
211
+ pattern.title = `${pattern.title} (resolved: ${note})`;
212
+ }
213
+ }
214
+ state.updatedAt = nowIso();
215
+ await this.persist();
216
+ return pattern;
217
+ }
218
+ async summary() {
219
+ const state = await this.ensureInitialized();
220
+ return {
221
+ project: state.project,
222
+ totalTickets: state.tickets.length,
223
+ activeTickets: state.tickets.filter((t) => t.status === "active").length,
224
+ triagedTickets: state.tickets.filter((t) => t.status === "triaged").length,
225
+ resolvedTickets: state.tickets.filter((t) => t.status === "resolved").length,
226
+ reopenedTickets: state.tickets.filter((t) => t.status === "reopened").length,
227
+ deferredTickets: state.tickets.filter((t) => t.status === "deferred").length,
228
+ openPatterns: state.patterns.filter((p) => p.status === "active").length,
229
+ stalledPatterns: state.patterns.filter((p) => p.status === "open").length,
230
+ resolvedPatterns: state.patterns.filter((p) => p.status === "resolved").length,
231
+ };
232
+ }
233
+ async getPattern(id) {
234
+ const state = await this.ensureInitialized();
235
+ return state.patterns.find((entry) => entry.id === id);
236
+ }
237
+ getConfig() {
238
+ return this.config;
239
+ }
240
+ async sourceConvergence(options = {}) {
241
+ const state = await this.ensureInitialized();
242
+ return (0, convergence_1.sourceConvergenceReport)(state.tickets, state.patterns, options);
243
+ }
244
+ async guardGaps(options = {}) {
245
+ const state = await this.ensureInitialized();
246
+ return (0, guards_1.guardGapReport)(state.tickets, this.config, options);
247
+ }
248
+ async searchKnowledge(options = {}) {
249
+ const state = await this.ensureInitialized();
250
+ return (0, knowledge_1.resolutionKnowledge)(state.tickets, options);
251
+ }
252
+ async knowledgeGaps(options = {}) {
253
+ const state = await this.ensureInitialized();
254
+ return (0, knowledge_1.knowledgeGaps)(state.tickets, options);
255
+ }
256
+ async related(rawId, options = {}) {
257
+ const state = await this.ensureInitialized();
258
+ const targetId = normalizeTicketInput(rawId, state.tickets);
259
+ const configured = this.config.priorArt;
260
+ return (0, prior_art_1.relatedTickets)(targetId, state.tickets, {
261
+ weights: { ...configured?.weights, ...options.weights },
262
+ minScore: options.minScore ?? configured?.minScore,
263
+ limit: options.limit,
264
+ });
265
+ }
266
+ // Resolves an id/alias and optionally applies a status change. Does NOT
267
+ // persist — callers mutate further (notes, timestamps, resolution fields) and
268
+ // persist once when done, so those mutations are never lost.
269
+ async transitionTicket(rawId, status) {
270
+ const state = await this.ensureInitialized();
271
+ const targetId = normalizeTicketInput(rawId, state.tickets);
272
+ const ticket = state.tickets.find((entry) => entry.id === targetId);
273
+ if (!ticket) {
274
+ throw new Error(`Ticket not found: ${rawId}`);
275
+ }
276
+ if (status && ticket.status !== status) {
277
+ ticket.status = status;
278
+ ticket.updatedAt = nowIso();
279
+ }
280
+ return ticket;
281
+ }
282
+ attachPattern(state, family, ticketId) {
283
+ if (!this.config.patterns.autoCreateByFamily) {
284
+ return undefined;
285
+ }
286
+ const normalizedFamily = family || this.config.patterns.defaultFamily;
287
+ let pattern = state.patterns.find((entry) => entry.family === normalizedFamily && entry.status !== "resolved");
288
+ if (!pattern) {
289
+ pattern = {
290
+ id: patternId(++state.nextPatternSeq),
291
+ family: normalizedFamily,
292
+ title: `Recurring ${normalizedFamily} issues`,
293
+ status: "open",
294
+ createdAt: nowIso(),
295
+ updatedAt: nowIso(),
296
+ ticketIds: [],
297
+ };
298
+ state.patterns.push(pattern);
299
+ }
300
+ if (!pattern.ticketIds.includes(ticketId)) {
301
+ pattern.ticketIds.push(ticketId);
302
+ if (pattern.ticketIds.length >= 2) {
303
+ pattern.status = "active";
304
+ }
305
+ }
306
+ pattern.updatedAt = nowIso();
307
+ return pattern.id;
308
+ }
309
+ async persist() {
310
+ if (!this.state) {
311
+ return;
312
+ }
313
+ this.state.updatedAt = nowIso();
314
+ await this.backend.save(this.state);
315
+ }
316
+ get nowState() {
317
+ return nowIso();
318
+ }
319
+ }
320
+ exports.AgentLoopStore = AgentLoopStore;
321
+ function normalizeTicketInput(raw, tickets) {
322
+ const normalized = raw.toUpperCase();
323
+ const match = /^([A-Z]+)-(\d{1,})$/.exec(normalized);
324
+ if (!match) {
325
+ return raw.toUpperCase();
326
+ }
327
+ const prefix = match[1];
328
+ const seq = match[2];
329
+ const canonical = `ISSUE-${seq}`;
330
+ if (prefix === "ISSUE") {
331
+ return canonical;
332
+ }
333
+ const found = tickets.find((ticket) => ticket.aliases.includes(`${prefix}-${seq}`));
334
+ if (found) {
335
+ return found.id;
336
+ }
337
+ return canonical;
338
+ }
339
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1,156 @@
1
+ export type TicketKind = "bug" | "feature" | "user_feedback" | "investigation" | "incident" | "tech_debt" | "task";
2
+ export type TicketStatus = "triaged" | "active" | "resolved" | "reopened" | "deferred";
3
+ export type PatternStatus = "open" | "active" | "resolved" | "reopened";
4
+ export type Severity = "low" | "medium" | "high" | "critical";
5
+ export type Confidence = "low" | "medium" | "high";
6
+ export type GuardStatus = "guard_added" | "guard_existing" | "guard_waived" | "guard_deferred" | "none";
7
+ export type NoteType = "hypothesis" | "related_history" | "prior_fix" | "triage" | "investigation";
8
+ export interface KindConfig {
9
+ kind: TicketKind;
10
+ defaultSeverity: Severity;
11
+ requiredFields?: string[];
12
+ }
13
+ /**
14
+ * A queue routes tickets to a single user-facing alias prefix. Queues are
15
+ * evaluated in order (first match wins); a `source` match takes the precedence
16
+ * of the queue it belongs to, so e.g. a `user_report`-sourced bug routes to the
17
+ * USER queue rather than ISSUE.
18
+ */
19
+ export interface QueueConfig {
20
+ /** Alias prefix, e.g. "USER", "DEV", "ISSUE". */
21
+ prefix: string;
22
+ /** Ticket kinds routed to this queue. */
23
+ kinds?: TicketKind[];
24
+ /** Sources routed to this queue, overriding kind routing. */
25
+ sources?: string[];
26
+ /** Fallback queue when nothing else matches. Exactly one queue should set this. */
27
+ default?: boolean;
28
+ }
29
+ export interface ProjectConfig {
30
+ projectName: string;
31
+ description: string;
32
+ defaultKind: TicketKind;
33
+ ticketKinds: KindConfig[];
34
+ queues: QueueConfig[];
35
+ sources: string[];
36
+ patterns: {
37
+ autoCreateByFamily: boolean;
38
+ defaultFamily: string;
39
+ };
40
+ /** Optional overrides for prior-art relatedness scoring. Core defaults apply when omitted. */
41
+ priorArt?: {
42
+ weights?: Partial<{
43
+ family: number;
44
+ pattern: number;
45
+ tag: number;
46
+ kind: number;
47
+ textOverlap: number;
48
+ }>;
49
+ minScore?: number;
50
+ };
51
+ /** Optional config-driven redaction. Library users can also inject a TicketRedactor directly. */
52
+ redaction?: {
53
+ patterns?: RedactionRule[];
54
+ };
55
+ /**
56
+ * Optional storage selection for the CLI/MCP. Prefer the `DATABASE_URL`
57
+ * environment variable for the connection string (it takes precedence) so
58
+ * secrets stay out of committed config.
59
+ */
60
+ storage?: {
61
+ databaseUrl?: string;
62
+ };
63
+ }
64
+ /** Context passed to a redactor so host implementations can vary behavior by field/ticket. */
65
+ export interface RedactionContext {
66
+ field: string;
67
+ ticketKind?: string;
68
+ source?: string;
69
+ }
70
+ /**
71
+ * Host-pluggable redaction hook. Core ships a no-op default and a config-driven
72
+ * pattern redactor; host apps own real PII/secret scrubbing.
73
+ */
74
+ export interface TicketRedactor {
75
+ redactText(value: string, context: RedactionContext): string;
76
+ redactJson(value: unknown, context: RedactionContext): unknown;
77
+ }
78
+ /** A single config-driven redaction rule (regex → replacement). */
79
+ export interface RedactionRule {
80
+ name?: string;
81
+ /** Regular-expression source. */
82
+ pattern: string;
83
+ /** Regex flags; defaults to "g". */
84
+ flags?: string;
85
+ /** Replacement string; defaults to "[redacted]". */
86
+ replacement?: string;
87
+ }
88
+ export interface TicketNote {
89
+ id: string;
90
+ type: NoteType;
91
+ body: string;
92
+ author?: string;
93
+ createdAt: string;
94
+ }
95
+ export interface Ticket {
96
+ id: string;
97
+ family: string;
98
+ kind: TicketKind;
99
+ source: string;
100
+ title: string;
101
+ summary: string;
102
+ severity: Severity;
103
+ confidence: Confidence;
104
+ status: TicketStatus;
105
+ createdAt: string;
106
+ updatedAt: string;
107
+ startedAt?: string;
108
+ resolvedAt?: string;
109
+ aliases: string[];
110
+ tags: string[];
111
+ notes: TicketNote[];
112
+ handoffText?: string;
113
+ guardStatus?: GuardStatus;
114
+ guardSummary?: string;
115
+ patternId?: string;
116
+ verification?: string;
117
+ reproducible?: boolean;
118
+ resolutionSummary?: string;
119
+ }
120
+ export interface Pattern {
121
+ id: string;
122
+ family: string;
123
+ title: string;
124
+ status: PatternStatus;
125
+ createdAt: string;
126
+ updatedAt: string;
127
+ ticketIds: string[];
128
+ }
129
+ export interface LoopState {
130
+ version: number;
131
+ project: string;
132
+ createdAt: string;
133
+ updatedAt: string;
134
+ nextTicketSeq: number;
135
+ nextPatternSeq: number;
136
+ tickets: Ticket[];
137
+ patterns: Pattern[];
138
+ }
139
+ export interface CreateTicketInput {
140
+ title: string;
141
+ summary: string;
142
+ family: string;
143
+ kind: TicketKind;
144
+ source: string;
145
+ severity?: Severity;
146
+ confidence?: Confidence;
147
+ tags?: string[];
148
+ handoffText?: string;
149
+ }
150
+ export interface ResolveInput {
151
+ id: string;
152
+ summary: string;
153
+ verification?: string;
154
+ guardStatus?: GuardStatus;
155
+ guardSummary?: string;
156
+ }
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,30 @@
1
+ # Architecture
2
+
3
+ IntiDev AgentLoops is organized around a tiny local persistence and CLI layer:
4
+
5
+ ## Core primitives
6
+
7
+ - `src/store.ts`
8
+ - owns durable state in `.agentloops/state.json`
9
+ - manages create/list/update transitions
10
+ - maintains ticket aliases and simple pattern grouping
11
+ - `src/config.ts`
12
+ - project-level config schema and defaults
13
+ - alias mapping and required fields
14
+ - `src/cli.ts`
15
+ - command parser and user-facing workflows
16
+ - `agentloop.config.json`
17
+ - local configuration created from template
18
+
19
+ ## Extensibility points
20
+
21
+ - ticket kinds and aliases can be customized in config
22
+ - custom required fields are represented through `requiredFields` per kind
23
+ - patterns currently group by family; teams can replace that in a fork or add source-level adapters
24
+
25
+ ## Planned extraction roadmap
26
+
27
+ 1. add MCP read-only tools for dashboard integrations,
28
+ 2. add pluggable storage adapters (SQLite/Postgres/HTTP API),
29
+ 3. add source adapters (Sentry, GitHub, Linear, Jira),
30
+ 4. split CLI/API/SDK packages.