@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 ADDED
@@ -0,0 +1,31 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. This project adheres to
4
+ [Semantic Versioning](https://semver.org/).
5
+
6
+ ## 0.1.0
7
+
8
+ First public release.
9
+
10
+ ### Core
11
+ - Durable ticket ledger with kinds, statuses, severities, confidences, and notes.
12
+ - Source-aware **queue aliases** (`ISSUE-` / `USER-` / `DEV-`) derived from kind and
13
+ source, over a single canonical `ISSUE-NNNNNN` id (config-driven `queues`).
14
+ - Family-based **Pattern** grouping (auto-activates at ≥2 tickets).
15
+ - Workflow transitions: begin, resolve, reopen, **defer**.
16
+ - **Source-convergence audit**, **guard-gap report**, **resolution knowledge** search +
17
+ gaps, and **prior-art** related-ticket lookup (config-tunable scoring).
18
+ - Copyable agent **handoff** prompts.
19
+ - Optional **redaction** hook (`TicketRedactor`) — config patterns or injected redactor.
20
+
21
+ ### Storage
22
+ - Pluggable `StateBackend`: filesystem JSON (default), in-memory, and **Postgres**
23
+ (public relational `ticket_*` schema; `pg` is an optional peer dependency).
24
+ - CLI and MCP run on Postgres automatically via `DATABASE_URL`.
25
+
26
+ ### Interfaces
27
+ - `agentloop` **CLI** (init, create, list, show, patterns, begin, resolve, reopen,
28
+ defer, note, guard, handoff, summary, convergence, guard-gaps, knowledge,
29
+ knowledge-gaps, related, dashboard, serve, config, mcp).
30
+ - **MCP server** (`agentloop mcp`): 9 read-only tools, plus 5 write tools behind `--write`.
31
+ - Zero-dependency **dashboard** (`agentloop dashboard` / `agentloop serve`).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Steven Vincent
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,221 @@
1
+ # IntiDev AgentLoops
2
+
3
+ ### Feedback Loops for Agentic Workflows
4
+
5
+ IntiDev AgentLoops is an open-source toolkit for tracking issues, features, and user feedback through an agent-friendly resolution loop. It is intentionally lightweight and project-agnostic, while remaining opinionated about reproducibility, resolution hygiene, and machine-readable handoff artifacts.
6
+
7
+ This repo is the first extractable iteration from our internal Tickets implementation, and is aimed at:
8
+
9
+ - developers building AI coding workflows,
10
+ - teams that need one loop for bugfixes, features, and support feedback,
11
+ - maintainers who want reusable resolution knowledge and structured evidence.
12
+
13
+ ## Why this project exists
14
+
15
+ Most bug trackers treat support, defects, and features as separate workflows.
16
+ IntiDev AgentLoops links them into one consistent lifecycle so humans and agents use the same ticket surface and knowledge.
17
+
18
+ ## Quick install
19
+
20
+ ```bash
21
+ npm install -g @stevenvincentone/intidev-agentloops
22
+ ```
23
+
24
+ Then run:
25
+
26
+ ```bash
27
+ agentloop init
28
+ agentloop create --title "Rendering regression in list pages" --summary "List pages lose anchors after parser update" --family "reader_rendering" --kind bug --source manual_admin
29
+ agentloop list
30
+ agentloop resolve ISSUE-000001 --summary "Added deterministic fallback for anchor selection"
31
+ ```
32
+
33
+ ## Try the convergence demo
34
+
35
+ Run a self-contained demo that seeds three independent intake loops — a smoke
36
+ test, a user report, and an agent proposal — all pointing at the same
37
+ `export_pipeline` family, and watch them converge into a single Pattern:
38
+
39
+ ```bash
40
+ npm run demo
41
+ ```
42
+
43
+ Expected output:
44
+
45
+ ```text
46
+ AgentLoops source-convergence demo
47
+ ==================================
48
+
49
+ Three intake loops, one underlying problem:
50
+
51
+ ISSUE-000001 bug source=smoke [export_pipeline]
52
+ Export smoke test times out on 500-page report
53
+ USER-000002 user_feedback source=user_report [export_pipeline]
54
+ Export fails for long reports
55
+ DEV-000003 feature source=agent [export_pipeline]
56
+ Stream the export pipeline instead of buffering
57
+
58
+ Converged into:
59
+ PATTERN-000001 ACTIVE (3 tickets) — Recurring export_pipeline issues
60
+
61
+ Summary: 3 tickets, 1 active pattern(s).
62
+ ```
63
+
64
+ The demo writes to a throwaway temp directory and leaves your repo untouched.
65
+ The same scenario is asserted in `test/demo.test.ts` against a committed golden
66
+ state fixture; run it with `npm test`.
67
+
68
+ ## Core concepts
69
+
70
+ - Ticket: one concrete work item (bug, feature, user feedback, incident, etc.)
71
+ - Pattern: a recurring cluster, often by family/domain
72
+ - Source: origin (`user_report`, `smoke`, `ci`, `agent`, `ingestion`, etc.)
73
+ - Alias: human-facing IDs such as `ISSUE-000001`, `DEV-000001`, `USER-000001`
74
+ - Handoff: copyable context block for an agent to continue execution
75
+
76
+ ## Commands
77
+
78
+ - `agentloop init` initialize `.agentloops` state and local config
79
+ - `agentloop create` add a ticket
80
+ - `agentloop list` view active and resolved work
81
+ - `agentloop begin <id>` mark triaged ticket as in-progress
82
+ - `agentloop resolve <id> --summary ...` mark resolved with evidence
83
+ - `agentloop reopen <id>` reopen and record a recurrence reason
84
+ - `agentloop defer <id> [--summary ...]` defer a ticket with an optional reason
85
+ - `agentloop note <id> --type ... --body ...` add context notes
86
+ - `agentloop guard <id> --guard-status ...` record guard decision
87
+ - `agentloop handoff <id>` print a copyable agent handoff prompt
88
+ - `agentloop patterns` list pattern groups
89
+ - `agentloop summary` print quick health metrics
90
+ - `agentloop convergence` report patterns whose tickets span multiple sources
91
+ - `agentloop guard-gaps` report resolved tickets missing a regression guard
92
+ - `agentloop knowledge` search how prior resolved tickets were fixed
93
+ - `agentloop knowledge-gaps` report resolved tickets lacking reusable knowledge
94
+ - `agentloop related <id>` find prior-art tickets related to one ticket
95
+ - `agentloop dashboard` write a standalone HTML dashboard
96
+ - `agentloop serve` serve the dashboard over HTTP
97
+ - `agentloop config` print resolved configuration
98
+ - `agentloop mcp` run the read-only MCP server over stdio
99
+
100
+ All commands support `--json` for machine-readable output where relevant.
101
+
102
+ ## MCP server (agent integration)
103
+
104
+ AgentLoops ships an [MCP](https://modelcontextprotocol.io) server so coding
105
+ agents (Claude Code, Codex, and other MCP clients) can use the ledger directly.
106
+ Writes are **opt-in**: the server is read-only unless you pass `--write`.
107
+
108
+ ```bash
109
+ agentloop mcp # read-only; speaks JSON-RPC over stdio, status to stderr
110
+ agentloop mcp --write # also expose the guarded write tools
111
+ ```
112
+
113
+ Read-only tools (annotated `readOnlyHint`):
114
+
115
+ | Tool | Purpose |
116
+ | --- | --- |
117
+ | `agentloop_summary` | loop health metrics (ticket and pattern counts) |
118
+ | `agentloop_list` | list tickets, optional `status` / `kind` filters |
119
+ | `agentloop_show` | one ticket (by `ISSUE-`/alias) or a `PATTERN-` id |
120
+ | `agentloop_handoff` | copyable agent handoff prompt for a ticket |
121
+ | `agentloop_convergence` | patterns whose tickets span multiple sources |
122
+ | `agentloop_guard_gaps` | resolved tickets missing a regression guard |
123
+ | `agentloop_search_knowledge` | search how prior resolved tickets were fixed |
124
+ | `agentloop_knowledge_gaps` | resolved tickets lacking reusable knowledge |
125
+ | `agentloop_related` | prior-art: tickets related to a given ticket |
126
+
127
+ Write tools (only registered with `--write`):
128
+
129
+ | Tool | Purpose |
130
+ | --- | --- |
131
+ | `agentloop_create` | create a ticket (`summary` required; `source` defaults to `agent`) |
132
+ | `agentloop_note` | append a non-resolution note |
133
+ | `agentloop_workflow` | transition a ticket (`active` / `reopened` / `deferred`) |
134
+ | `agentloop_resolve` | resolve with a summary, optional verification + guard |
135
+ | `agentloop_guard` | record a regression-guard decision |
136
+
137
+ Each result is a JSON envelope with `schemaVersion` and `generatedAt`. The server
138
+ reads/writes state from the `.agentloops/state.json` in its working directory, so
139
+ run it from your project root (or where you ran `agentloop init`).
140
+
141
+ Register it with an MCP client, for example Claude Code:
142
+
143
+ ```bash
144
+ claude mcp add agentloop -- agentloop mcp
145
+ ```
146
+
147
+ or directly in a client config:
148
+
149
+ ```json
150
+ {
151
+ "mcpServers": {
152
+ "agentloop": { "command": "agentloop", "args": ["mcp"] }
153
+ }
154
+ }
155
+ ```
156
+
157
+ ## Dashboard
158
+
159
+ A zero-dependency reference UI renders the ledger as a single self-contained HTML
160
+ page — queues (Issues / User / Development), patterns, source convergence, and
161
+ guard gaps — with no build step or frontend framework.
162
+
163
+ ```bash
164
+ agentloop dashboard --out dashboard.html # write a static snapshot, open in a browser
165
+ agentloop serve --port 4319 # live dashboard + read-only JSON at /api/*
166
+ ```
167
+
168
+ Both work over either storage backend. All ticket content is HTML-escaped. For a
169
+ richer or embeddable UI, the `renderDashboard(data)` and `createDashboardServer(store)`
170
+ exports can be built upon.
171
+
172
+ ## Data model
173
+
174
+ State is stored in your working directory at `.agentloops/state.json` by default.
175
+ The store persists through a pluggable `StateBackend`, so the same ledger can run
176
+ over the filesystem, an in-memory store, or **Postgres** (a relational `ticket_*`
177
+ schema) — see [docs/postgres.md](docs/postgres.md).
178
+
179
+ For local project settings, copy and customize:
180
+
181
+ ```bash
182
+ cp agentloop.config.json.example agentloop.config.json
183
+ ```
184
+
185
+ The config controls:
186
+
187
+ - project naming
188
+ - ticket kinds and aliases (`ISSUE`, `DEV`, `USER`, etc.)
189
+ - default family for auto-grouping
190
+ - configured sources
191
+
192
+ ## Privacy and redaction
193
+
194
+ By default AgentLoops stores ticket text as-is and makes no model or network calls.
195
+ Host apps own redaction. Two ways to scrub sensitive content (PII, secrets) before
196
+ it is written to `.agentloops/state.json`:
197
+
198
+ - **Config-driven** — add regex rules under `redaction.patterns` in
199
+ `agentloop.config.json`; they apply to titles, summaries, notes, resolutions,
200
+ and guard summaries on every write (CLI and MCP included):
201
+
202
+ ```json
203
+ { "redaction": { "patterns": [{ "pattern": "[\\w.]+@[\\w.]+\\.[a-z]+", "replacement": "[email]" }] } }
204
+ ```
205
+
206
+ - **Code-driven** — library users can inject a `TicketRedactor`:
207
+ `new AgentLoopStore(cwd, config, { redactor })`.
208
+
209
+ ## Contributing
210
+
211
+ Open issues and PRs are welcome.
212
+
213
+ When adding new sources or fields, include:
214
+
215
+ 1. a config-backed approach, not hardcoded assumptions,
216
+ 2. a short schema note in docs,
217
+ 3. a concise example command and expected output.
218
+
219
+ ## License
220
+
221
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,31 @@
1
+ {
2
+ "projectName": "IntiDev AgentLoops",
3
+ "description": "Feedback loops for agentic workflows",
4
+ "defaultKind": "bug",
5
+ "ticketKinds": [
6
+ { "kind": "bug", "defaultSeverity": "high", "requiredFields": ["summary"] },
7
+ { "kind": "feature", "defaultSeverity": "medium", "requiredFields": ["summary"] },
8
+ { "kind": "user_feedback", "defaultSeverity": "high", "requiredFields": ["summary"] },
9
+ { "kind": "investigation", "defaultSeverity": "medium", "requiredFields": ["summary"] },
10
+ { "kind": "incident", "defaultSeverity": "critical", "requiredFields": ["summary"] },
11
+ { "kind": "tech_debt", "defaultSeverity": "medium", "requiredFields": ["summary"] },
12
+ { "kind": "task", "defaultSeverity": "medium", "requiredFields": ["summary"] }
13
+ ],
14
+ "queues": [
15
+ { "prefix": "USER", "kinds": ["user_feedback"], "sources": ["user_report"] },
16
+ { "prefix": "DEV", "kinds": ["feature", "task", "investigation", "tech_debt"] },
17
+ { "prefix": "ISSUE", "kinds": ["bug", "incident"], "default": true }
18
+ ],
19
+ "sources": [
20
+ "user_report",
21
+ "manual_admin",
22
+ "smoke",
23
+ "agent",
24
+ "ci",
25
+ "ingestion"
26
+ ],
27
+ "patterns": {
28
+ "autoCreateByFamily": true,
29
+ "defaultFamily": "general"
30
+ }
31
+ }
@@ -0,0 +1,25 @@
1
+ import { ProjectConfig } from "./types";
2
+ /** Zero-pad a ticket sequence number to the canonical width. */
3
+ export declare function padSeq(seq: number): string;
4
+ /** The canonical key for a ticket is always `ISSUE-NNNNNN`, regardless of queue. */
5
+ export declare function canonicalKey(seq: number): string;
6
+ /**
7
+ * Resolve the single queue-alias prefix for a ticket from its kind and source.
8
+ *
9
+ * Ported from Inti's `TicketAliases` (USER → DEV → ISSUE precedence): a queue
10
+ * matches when the ticket's `source` is in `queue.sources` OR its `kind` is in
11
+ * `queue.kinds`. Queues are tried in config order, so the source override (e.g.
12
+ * `user_report` → USER) wins for any kind. Falls back to the `default` queue.
13
+ */
14
+ export declare function resolveQueuePrefix(input: {
15
+ kind: string;
16
+ source?: string;
17
+ }, config: ProjectConfig): string;
18
+ /**
19
+ * Build the user-facing alias key(s) for a ticket. Currently a single queue
20
+ * alias; returned as an array to keep the `Ticket.aliases` shape stable.
21
+ */
22
+ export declare function deriveAliases(input: {
23
+ kind: string;
24
+ source?: string;
25
+ }, seq: number, config: ProjectConfig): string[];
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.padSeq = padSeq;
4
+ exports.canonicalKey = canonicalKey;
5
+ exports.resolveQueuePrefix = resolveQueuePrefix;
6
+ exports.deriveAliases = deriveAliases;
7
+ const SEQ_PAD = 6;
8
+ /** Zero-pad a ticket sequence number to the canonical width. */
9
+ function padSeq(seq) {
10
+ return String(seq).padStart(SEQ_PAD, "0");
11
+ }
12
+ /** The canonical key for a ticket is always `ISSUE-NNNNNN`, regardless of queue. */
13
+ function canonicalKey(seq) {
14
+ return `ISSUE-${padSeq(seq)}`;
15
+ }
16
+ /**
17
+ * Resolve the single queue-alias prefix for a ticket from its kind and source.
18
+ *
19
+ * Ported from Inti's `TicketAliases` (USER → DEV → ISSUE precedence): a queue
20
+ * matches when the ticket's `source` is in `queue.sources` OR its `kind` is in
21
+ * `queue.kinds`. Queues are tried in config order, so the source override (e.g.
22
+ * `user_report` → USER) wins for any kind. Falls back to the `default` queue.
23
+ */
24
+ function resolveQueuePrefix(input, config) {
25
+ const source = input.source ?? "";
26
+ for (const queue of config.queues) {
27
+ if (queue.sources?.includes(source))
28
+ return queue.prefix;
29
+ if (queue.kinds?.includes(input.kind))
30
+ return queue.prefix;
31
+ }
32
+ const fallback = config.queues.find((queue) => queue.default);
33
+ return fallback?.prefix ?? "ISSUE";
34
+ }
35
+ /**
36
+ * Build the user-facing alias key(s) for a ticket. Currently a single queue
37
+ * alias; returned as an array to keep the `Ticket.aliases` shape stable.
38
+ */
39
+ function deriveAliases(input, seq, config) {
40
+ return [`${resolveQueuePrefix(input, config)}-${padSeq(seq)}`];
41
+ }
42
+ //# sourceMappingURL=aliases.js.map
@@ -0,0 +1,29 @@
1
+ import { LoopState } from "./types";
2
+ /**
3
+ * Persistence port for the ledger. `AgentLoopStore` holds the whole `LoopState`
4
+ * in memory and delegates loading/saving to a backend, so the same domain logic
5
+ * runs over the filesystem, an in-memory store, or Postgres.
6
+ */
7
+ export interface StateBackend {
8
+ /** Return the persisted state, or null if nothing has been stored yet. */
9
+ load(): Promise<LoopState | null>;
10
+ /** Persist the full state snapshot. */
11
+ save(state: LoopState): Promise<void>;
12
+ /** Optional one-time setup (e.g. create schema). No-op for filesystem/memory. */
13
+ migrate?(): Promise<void>;
14
+ }
15
+ /** Default backend: JSON at `<cwd>/.agentloops/state.json`. */
16
+ export declare class FilesystemStateBackend implements StateBackend {
17
+ private readonly dirPath;
18
+ private readonly statePath;
19
+ constructor(cwd: string, dir?: string, fileName?: string);
20
+ load(): Promise<LoopState | null>;
21
+ save(state: LoopState): Promise<void>;
22
+ }
23
+ /** Ephemeral backend, handy for tests and short-lived processes. */
24
+ export declare class MemoryStateBackend implements StateBackend {
25
+ private state;
26
+ constructor(initial?: LoopState | null);
27
+ load(): Promise<LoopState | null>;
28
+ save(state: LoopState): Promise<void>;
29
+ }
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MemoryStateBackend = exports.FilesystemStateBackend = void 0;
4
+ const node_fs_1 = require("node:fs");
5
+ const node_path_1 = require("node:path");
6
+ /** Default backend: JSON at `<cwd>/.agentloops/state.json`. */
7
+ class FilesystemStateBackend {
8
+ dirPath;
9
+ statePath;
10
+ constructor(cwd, dir = ".agentloops", fileName = "state.json") {
11
+ this.dirPath = (0, node_path_1.join)(cwd, dir);
12
+ this.statePath = (0, node_path_1.join)(this.dirPath, fileName);
13
+ }
14
+ async load() {
15
+ if (!(0, node_fs_1.existsSync)(this.statePath))
16
+ return null;
17
+ const text = await node_fs_1.promises.readFile(this.statePath, "utf-8");
18
+ return JSON.parse(text);
19
+ }
20
+ async save(state) {
21
+ await node_fs_1.promises.mkdir(this.dirPath, { recursive: true });
22
+ await node_fs_1.promises.writeFile(this.statePath, JSON.stringify(state, null, 2), "utf-8");
23
+ }
24
+ }
25
+ exports.FilesystemStateBackend = FilesystemStateBackend;
26
+ /** Ephemeral backend, handy for tests and short-lived processes. */
27
+ class MemoryStateBackend {
28
+ state;
29
+ constructor(initial = null) {
30
+ this.state = initial ? structuredClone(initial) : null;
31
+ }
32
+ async load() {
33
+ return this.state ? structuredClone(this.state) : null;
34
+ }
35
+ async save(state) {
36
+ this.state = structuredClone(state);
37
+ }
38
+ }
39
+ exports.MemoryStateBackend = MemoryStateBackend;
40
+ //# sourceMappingURL=backend.js.map
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};