@alexgorbatchev/pi-agentation 3.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alex Gorbatchev
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,72 @@
1
+ # pi plugin for Agentation Fork
2
+
3
+ A pi extension that continuously runs an Agentation fix loop by repeatedly sending:
4
+
5
+ - `/skill:agentation-fix-loop <project-id>`
6
+
7
+ It starts automatically when the session starts, resolves the project ID for the current repository (searching for `<Agentation projectId=... />`), and keeps re-queuing the same project-scoped prompt after each agent run until pi exits (or you stop it).
8
+
9
+ See:
10
+
11
+ - [Agentation Fork](https://github.com/alexgorbatchev/agentation)
12
+ - [CLI](https://github.com/alexgorbatchev/agentation-cli)
13
+ - [Agentation Skills](https://github.com/alexgorbatchev/agentation-skills)
14
+
15
+ > [!IMPORTANT]
16
+ > This loops AI until manually stopped and so it can consume tokens while idling. Don't forget to stop it when you no longer using it.
17
+
18
+ ## Behavior
19
+
20
+ - The launcher (`pi-agentation`) injects the local packaged fix-loop skill via `--skill`
21
+ - Extension checks that `/skill:agentation-fix-loop` is available before running
22
+ - On session start/switch/fork, the extension:
23
+ - runs `agentation projects --json`
24
+ - runs `rg` to discover literal `projectId="..."` or `projectId='...'` values in the repo
25
+ - intersects both lists
26
+ - auto-starts if exactly one project matches, otherwise prompts you to choose in the TUI
27
+ - The resolved project ID is stored in the current Pi session so reloads/resume do not re-prompt that same session
28
+ - On `agent_end`: sends the next project-scoped loop prompt
29
+ - On `session_shutdown`: stops loop automatically
30
+ - If the skill is missing, no repo project IDs are found, or no discovered repo IDs are known to Agentation yet: plugin requests shutdown and exits with code `1`
31
+
32
+ ## Commands
33
+
34
+ - `/agentation-loop-start` — resume/start looping
35
+ - `/agentation-loop-stop` — pause looping
36
+
37
+ ## Installation
38
+
39
+ Install both project packages:
40
+
41
+ ```bash
42
+ npm install -D @alexgorbatchev/agentation @alexgorbatchev/pi-agentation
43
+ ```
44
+
45
+ `@alexgorbatchev/pi-agentation` ships its own packaged copy of the fix-loop skill. That file is synced from [`@alexgorbatchev/agentation-skills`](https://github.com/alexgorbatchev/agentation-skills) during packaging, so you do not need to install the skill package separately.
46
+
47
+ Required executables on `PATH`:
48
+
49
+ - `pi`
50
+ - `agentation`
51
+ - `rg`
52
+
53
+ The `agentation` [CLI](https://github.com/alexgorbatchev/agentation-cli) is distributed separately from these npm packages and must be downloaded and placed on your `PATH`.
54
+
55
+ ## Usage
56
+
57
+ Before running the pi, you need to start the [CLI](https://github.com/alexgorbatchev/agentation-cli) and connect from the front end which has `<Agentation projectId="..." />` at least once in the last 24h. Then run the launcher from your project:
58
+
59
+ ```bash
60
+ npx pi-agentation
61
+ ```
62
+
63
+ If your shell already exposes local package binaries on `PATH`, you can run:
64
+
65
+ ```bash
66
+ pi-agentation
67
+ ```
68
+
69
+ ## Notes
70
+
71
+ - This loop is intentionally persistent and can consume tokens quickly.
72
+ - Use `/agentation-loop-stop` if you want to pause it without exiting Pi.
package/agentation.ts ADDED
@@ -0,0 +1,432 @@
1
+ import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
2
+ import process from "node:process";
3
+
4
+ const LOOP_SKILL_NAME = "agentation-fix-loop";
5
+ const LOOP_PROMPT = `/skill:${LOOP_SKILL_NAME}`;
6
+ const PROJECT_SELECTION_ENTRY_TYPE = "agentation-project-selection";
7
+ const PROJECT_ID_PATTERN = /^projectId=(?:"([^"\r\n]+)"|'([^'\r\n]+)')$/;
8
+
9
+ type ExecResult = Awaited<ReturnType<ExtensionAPI["exec"]>>;
10
+
11
+ type CommandOutcome = {
12
+ code: number | undefined;
13
+ errorMessage?: string;
14
+ killed: boolean;
15
+ stderr: string;
16
+ stdout: string;
17
+ };
18
+
19
+ interface IProjectSelectionData {
20
+ projectId: string;
21
+ }
22
+
23
+ export default function agentation(pi: ExtensionAPI): void {
24
+ let currentProjectId: string | null = null;
25
+ let isLoopEnabled = true;
26
+
27
+ const isLoopSkillAvailable = (): boolean => {
28
+ return pi.getCommands().some((command) => {
29
+ if (command.source !== "skill") {
30
+ return false;
31
+ }
32
+
33
+ return command.name === LOOP_SKILL_NAME || command.name === `skill:${LOOP_SKILL_NAME}`;
34
+ });
35
+ };
36
+
37
+ const reportError = (ctx: ExtensionContext, message: string): void => {
38
+ console.error(message);
39
+ ctx.ui.notify(message, "error");
40
+ };
41
+
42
+ const shutdownForFatalError = (ctx: ExtensionContext, message: string): void => {
43
+ currentProjectId = null;
44
+ isLoopEnabled = false;
45
+ process.exitCode = 1;
46
+ reportError(ctx, message);
47
+ ctx.shutdown();
48
+ };
49
+
50
+ const ensureLoopSkillAvailable = (ctx: ExtensionContext): boolean => {
51
+ if (isLoopSkillAvailable()) {
52
+ return true;
53
+ }
54
+
55
+ shutdownForFatalError(ctx, `Missing required skill ${LOOP_PROMPT}. Exiting.`);
56
+ return false;
57
+ };
58
+
59
+ const queueLoopPrompt = (projectId: string, deliverAsFollowUp: boolean): void => {
60
+ if (!isLoopEnabled) {
61
+ return;
62
+ }
63
+
64
+ const loopPrompt = `${LOOP_PROMPT} ${projectId}`;
65
+ if (deliverAsFollowUp) {
66
+ pi.sendUserMessage(loopPrompt, { deliverAs: "followUp" });
67
+ return;
68
+ }
69
+
70
+ pi.sendUserMessage(loopPrompt);
71
+ };
72
+
73
+ const persistProjectSelection = (projectId: string): void => {
74
+ if (currentProjectId === projectId) {
75
+ return;
76
+ }
77
+
78
+ currentProjectId = projectId;
79
+ pi.appendEntry(PROJECT_SELECTION_ENTRY_TYPE, { projectId });
80
+ };
81
+
82
+ const restoreProjectSelection = (ctx: ExtensionContext): string | null => {
83
+ let latestProjectId: string | null = null;
84
+ let latestTimestamp = "";
85
+
86
+ for (const entry of ctx.sessionManager.getBranch()) {
87
+ if (entry.type !== "custom" || entry.customType !== PROJECT_SELECTION_ENTRY_TYPE) {
88
+ continue;
89
+ }
90
+
91
+ if (!isProjectSelectionData(entry.data)) {
92
+ continue;
93
+ }
94
+
95
+ const normalizedProjectId = normalizeProjectId(entry.data.projectId);
96
+ if (normalizedProjectId === null) {
97
+ continue;
98
+ }
99
+
100
+ if (entry.timestamp >= latestTimestamp) {
101
+ latestTimestamp = entry.timestamp;
102
+ latestProjectId = normalizedProjectId;
103
+ }
104
+ }
105
+
106
+ currentProjectId = latestProjectId;
107
+ return latestProjectId;
108
+ };
109
+
110
+ const execCommand = async (command: string, args: string[]): Promise<CommandOutcome> => {
111
+ try {
112
+ const result: ExecResult = await pi.exec(command, args);
113
+ return {
114
+ code: result.code,
115
+ killed: result.killed,
116
+ stderr: result.stderr,
117
+ stdout: result.stdout,
118
+ };
119
+ } catch (error) {
120
+ return {
121
+ code: undefined,
122
+ errorMessage: error instanceof Error ? error.message : String(error),
123
+ killed: false,
124
+ stderr: "",
125
+ stdout: "",
126
+ };
127
+ }
128
+ };
129
+
130
+ const listKnownProjectIds = async (): Promise<{ errorMessage?: string; projectIds: string[] }> => {
131
+ let projectsResult = await execCommand("agentation", ["projects", "--json"]);
132
+ if (!didCommandSucceed(projectsResult)) {
133
+ await execCommand("agentation", ["start", "--background"]);
134
+ projectsResult = await execCommand("agentation", ["projects", "--json"]);
135
+ }
136
+
137
+ if (!didCommandSucceed(projectsResult)) {
138
+ const failureMessage = formatCommandOutcome(projectsResult);
139
+ return {
140
+ errorMessage: `Failed to load Agentation projects via \`agentation projects --json\`: ${failureMessage}`,
141
+ projectIds: [],
142
+ };
143
+ }
144
+
145
+ const projectIds = parseJsonStringArray(projectsResult.stdout);
146
+ if (projectIds === null) {
147
+ return {
148
+ errorMessage: "`agentation projects --json` returned invalid JSON.",
149
+ projectIds: [],
150
+ };
151
+ }
152
+
153
+ return { projectIds };
154
+ };
155
+
156
+ const discoverRepoProjectIds = async (): Promise<{ errorMessage?: string; projectIds: string[] }> => {
157
+ const rgResult = await execCommand("rg", [
158
+ "-o",
159
+ "--no-filename",
160
+ "--glob",
161
+ "*.{tsx,jsx}",
162
+ "projectId=(?:\"[^\"]+\"|'[^']+')",
163
+ ".",
164
+ ]);
165
+
166
+ if (didCommandSucceed(rgResult)) {
167
+ return { projectIds: extractProjectIdsFromRgOutput(rgResult.stdout) };
168
+ }
169
+
170
+ if (rgResult.code === 1 && !rgResult.killed && rgResult.errorMessage === undefined) {
171
+ return { projectIds: [] };
172
+ }
173
+
174
+ return {
175
+ errorMessage: `Failed to discover project IDs via \`rg\`: ${formatCommandOutcome(rgResult)}`,
176
+ projectIds: [],
177
+ };
178
+ };
179
+
180
+ const resolveProjectId = async (ctx: ExtensionContext): Promise<string | null> => {
181
+ const knownProjectsResult = await listKnownProjectIds();
182
+ if (knownProjectsResult.errorMessage !== undefined) {
183
+ shutdownForFatalError(ctx, knownProjectsResult.errorMessage);
184
+ return null;
185
+ }
186
+
187
+ const repoProjectsResult = await discoverRepoProjectIds();
188
+ if (repoProjectsResult.errorMessage !== undefined) {
189
+ shutdownForFatalError(ctx, repoProjectsResult.errorMessage);
190
+ return null;
191
+ }
192
+
193
+ if (repoProjectsResult.projectIds.length === 0) {
194
+ const missingProjectIdMessage = [
195
+ "No literal Agentation `projectId` values were found in this repository.",
196
+ "Agentation requires an approved literal `projectId` pattern.",
197
+ ].join(" ");
198
+ shutdownForFatalError(ctx, missingProjectIdMessage);
199
+ return null;
200
+ }
201
+
202
+ const candidateProjectIds = intersectProjectIds(repoProjectsResult.projectIds, knownProjectsResult.projectIds);
203
+ if (candidateProjectIds.length === 0) {
204
+ const repoProjectIds = repoProjectsResult.projectIds.join(", ");
205
+ const unknownProjectMessage = [
206
+ `Found repository project IDs (${repoProjectIds}), but none are known to Agentation yet.`,
207
+ "Open the UI so it connects to the server at least once, then retry.",
208
+ ].join(" ");
209
+ shutdownForFatalError(ctx, unknownProjectMessage);
210
+ return null;
211
+ }
212
+
213
+ if (candidateProjectIds.length === 1) {
214
+ return candidateProjectIds[0] ?? null;
215
+ }
216
+
217
+ if (!ctx.hasUI) {
218
+ const candidateProjectLabel = candidateProjectIds.join(", ");
219
+ const selectionMessage = [
220
+ `Multiple Agentation projects matched this repository (${candidateProjectLabel}),`,
221
+ "but no interactive UI is available to choose one.",
222
+ ].join(" ");
223
+ shutdownForFatalError(ctx, selectionMessage);
224
+ return null;
225
+ }
226
+
227
+ const selectedProjectId = await ctx.ui.select("Select Agentation project", candidateProjectIds);
228
+ if (selectedProjectId === undefined) {
229
+ shutdownForFatalError(ctx, "Agentation project selection was cancelled.");
230
+ return null;
231
+ }
232
+
233
+ return selectedProjectId;
234
+ };
235
+
236
+ const initializeLoopForSession = async (ctx: ExtensionContext): Promise<void> => {
237
+ restoreProjectSelection(ctx);
238
+
239
+ if (!isLoopEnabled) {
240
+ return;
241
+ }
242
+
243
+ if (!ensureLoopSkillAvailable(ctx)) {
244
+ return;
245
+ }
246
+
247
+ let projectId = currentProjectId;
248
+ if (projectId === null) {
249
+ projectId = await resolveProjectId(ctx);
250
+ if (projectId === null) {
251
+ return;
252
+ }
253
+ persistProjectSelection(projectId);
254
+ }
255
+
256
+ ctx.ui.notify(`Agentation loop started for ${projectId}`, "info");
257
+ queueLoopPrompt(projectId, !ctx.isIdle());
258
+ };
259
+
260
+ pi.on("session_start", async (_event, ctx) => {
261
+ await initializeLoopForSession(ctx);
262
+ });
263
+
264
+ pi.on("session_switch", async (_event, ctx) => {
265
+ await initializeLoopForSession(ctx);
266
+ });
267
+
268
+ pi.on("session_fork", async (_event, ctx) => {
269
+ await initializeLoopForSession(ctx);
270
+ });
271
+
272
+ pi.on("agent_end", async (_event, ctx) => {
273
+ if (!isLoopEnabled) {
274
+ return;
275
+ }
276
+
277
+ if (!ensureLoopSkillAvailable(ctx)) {
278
+ return;
279
+ }
280
+
281
+ if (currentProjectId === null) {
282
+ reportError(ctx, "Agentation loop is enabled, but no project ID is selected for this session.");
283
+ return;
284
+ }
285
+
286
+ queueLoopPrompt(currentProjectId, !ctx.isIdle());
287
+ });
288
+
289
+ pi.on("session_shutdown", async () => {
290
+ currentProjectId = null;
291
+ isLoopEnabled = false;
292
+ });
293
+
294
+ pi.registerCommand("agentation-loop-start", {
295
+ description: "Start the automatic Agentation fix loop",
296
+ handler: async (_args, ctx) => {
297
+ if (isLoopEnabled) {
298
+ ctx.ui.notify("Agentation loop is already running", "info");
299
+ return;
300
+ }
301
+
302
+ if (!ensureLoopSkillAvailable(ctx)) {
303
+ return;
304
+ }
305
+
306
+ let projectId = currentProjectId ?? restoreProjectSelection(ctx);
307
+ if (projectId === null) {
308
+ projectId = await resolveProjectId(ctx);
309
+ if (projectId === null) {
310
+ return;
311
+ }
312
+ persistProjectSelection(projectId);
313
+ }
314
+
315
+ isLoopEnabled = true;
316
+ ctx.ui.notify(`Agentation loop resumed for ${projectId}`, "info");
317
+ queueLoopPrompt(projectId, !ctx.isIdle());
318
+ },
319
+ });
320
+
321
+ pi.registerCommand("agentation-loop-stop", {
322
+ description: "Stop the automatic Agentation fix loop",
323
+ handler: async (_args, ctx) => {
324
+ isLoopEnabled = false;
325
+ ctx.ui.notify("Agentation loop paused", "warning");
326
+ },
327
+ });
328
+ }
329
+
330
+ function didCommandSucceed(commandOutcome: CommandOutcome): boolean {
331
+ return commandOutcome.code === 0 && !commandOutcome.killed && commandOutcome.errorMessage === undefined;
332
+ }
333
+
334
+ function formatCommandOutcome(commandOutcome: CommandOutcome): string {
335
+ if (commandOutcome.errorMessage !== undefined) {
336
+ return commandOutcome.errorMessage;
337
+ }
338
+
339
+ const stderr = commandOutcome.stderr.trim();
340
+ if (stderr !== "") {
341
+ return stderr;
342
+ }
343
+
344
+ const stdout = commandOutcome.stdout.trim();
345
+ if (stdout !== "") {
346
+ return stdout;
347
+ }
348
+
349
+ if (commandOutcome.killed) {
350
+ return "command was killed";
351
+ }
352
+
353
+ if (commandOutcome.code !== undefined) {
354
+ return `exit code ${commandOutcome.code}`;
355
+ }
356
+
357
+ return "unknown failure";
358
+ }
359
+
360
+ function normalizeProjectId(projectId: string): string | null {
361
+ const trimmedProjectId = projectId.trim();
362
+ if (trimmedProjectId === "") {
363
+ return null;
364
+ }
365
+
366
+ return trimmedProjectId;
367
+ }
368
+
369
+ function normalizeProjectIds(projectIds: readonly string[]): string[] {
370
+ const normalizedProjectIds = new Set<string>();
371
+ for (const projectId of projectIds) {
372
+ const normalizedProjectId = normalizeProjectId(projectId);
373
+ if (normalizedProjectId !== null) {
374
+ normalizedProjectIds.add(normalizedProjectId);
375
+ }
376
+ }
377
+
378
+ return Array.from(normalizedProjectIds).sort((leftProjectId, rightProjectId) => {
379
+ return leftProjectId.localeCompare(rightProjectId);
380
+ });
381
+ }
382
+
383
+ function intersectProjectIds(repoProjectIds: readonly string[], knownProjectIds: readonly string[]): string[] {
384
+ const knownProjectIdSet = new Set(normalizeProjectIds(knownProjectIds));
385
+ return normalizeProjectIds(repoProjectIds).filter((projectId) => {
386
+ return knownProjectIdSet.has(projectId);
387
+ });
388
+ }
389
+
390
+ function extractProjectIdsFromRgOutput(output: string): string[] {
391
+ const projectIds: string[] = [];
392
+ for (const line of output.split(/\r?\n/)) {
393
+ const trimmedLine = line.trim();
394
+ if (trimmedLine === "") {
395
+ continue;
396
+ }
397
+
398
+ const match = PROJECT_ID_PATTERN.exec(trimmedLine);
399
+ const extractedProjectId = match?.[1] ?? match?.[2];
400
+ if (extractedProjectId !== undefined) {
401
+ projectIds.push(extractedProjectId);
402
+ }
403
+ }
404
+
405
+ return normalizeProjectIds(projectIds);
406
+ }
407
+
408
+ function isRecord(value: unknown): value is Record<string, unknown> {
409
+ return typeof value === "object" && value !== null;
410
+ }
411
+
412
+ function isStringArray(value: unknown): value is string[] {
413
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
414
+ }
415
+
416
+ function isProjectSelectionData(value: unknown): value is IProjectSelectionData {
417
+ if (!isRecord(value)) {
418
+ return false;
419
+ }
420
+
421
+ const projectId = value["projectId"];
422
+ return typeof projectId === "string" && projectId.trim() !== "";
423
+ }
424
+
425
+ function parseJsonStringArray(jsonText: string): string[] | null {
426
+ try {
427
+ const parsed = JSON.parse(jsonText);
428
+ return isStringArray(parsed) ? normalizeProjectIds(parsed) : null;
429
+ } catch {
430
+ return null;
431
+ }
432
+ }
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SOURCE_PATH="${BASH_SOURCE[0]}"
5
+ while [[ -L "${SOURCE_PATH}" ]]; do
6
+ SOURCE_DIRECTORY="$(cd -P "$(dirname "${SOURCE_PATH}")" && pwd)"
7
+ SOURCE_PATH="$(readlink "${SOURCE_PATH}")"
8
+
9
+ if [[ "${SOURCE_PATH}" != /* ]]; then
10
+ SOURCE_PATH="${SOURCE_DIRECTORY}/${SOURCE_PATH}"
11
+ fi
12
+ done
13
+
14
+ SCRIPT_DIR="$(cd -P "$(dirname "${SOURCE_PATH}")" && pwd)"
15
+ PACKAGE_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
16
+
17
+ exec pi \
18
+ -e "${PACKAGE_ROOT}/agentation.ts" \
19
+ --skill "${PACKAGE_ROOT}/skills/agentation-fix-loop/SKILL.md" \
20
+ "$@"
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@alexgorbatchev/pi-agentation",
3
+ "version": "3.0.0",
4
+ "description": "pi extension launcher for the Agentation fix loop",
5
+ "license": "MIT",
6
+ "keywords": [
7
+ "pi-package",
8
+ "pi-extension",
9
+ "agentation"
10
+ ],
11
+ "bin": {
12
+ "pi-agentation": "./bin/pi-agentation"
13
+ },
14
+ "pi": {
15
+ "extensions": [
16
+ "./agentation.ts"
17
+ ],
18
+ "skills": [
19
+ "./skills"
20
+ ]
21
+ },
22
+ "files": [
23
+ "agentation.ts",
24
+ "README.md",
25
+ "bin/pi-agentation",
26
+ "skills/agentation-fix-loop/SKILL.md"
27
+ ],
28
+ "peerDependencies": {
29
+ "@mariozechner/pi-coding-agent": "*"
30
+ },
31
+ "devDependencies": {
32
+ "@alexgorbatchev/agentation-skills": "^1.0.0",
33
+ "@mariozechner/pi-coding-agent": "^0.61.0"
34
+ },
35
+ "scripts": {
36
+ "check": "npm run sync-skill && npm run verify:package && npm run verify:launcher",
37
+ "prepack": "npm run sync-skill",
38
+ "sync-skill": "node ./scripts/syncBundledSkill.js",
39
+ "verify:package": "npm pack --dry-run > /dev/null",
40
+ "verify:launcher": "PI_OFFLINE=1 ./bin/pi-agentation --list-models > /dev/null"
41
+ },
42
+ "publishConfig": {
43
+ "access": "public"
44
+ }
45
+ }
@@ -0,0 +1,199 @@
1
+ ---
2
+ name: agentation-fix-loop
3
+ description: >-
4
+ Watch for Agentation annotations and fix each one using the Agentation CLI.
5
+ Runs `agentation watch` in a loop — acknowledges each annotation, makes the
6
+ code fix, then resolves it. Use when the user says "watch annotations",
7
+ "fix annotations", "annotation loop", "agentation fix loop", or wants
8
+ autonomous processing of design feedback from the Agentation toolbar.
9
+ targets:
10
+ - '*'
11
+ ---
12
+
13
+ # Agentation Fix Loop (CLI)
14
+
15
+ Watch for annotations from the Agentation toolbar and fix each one in the codebase using the `agentation` CLI.
16
+
17
+ If this skill is invoked as `/skill:agentation-fix-loop <project-id>`, treat the user-supplied argument as the authoritative project ID for this run and skip repo discovery.
18
+
19
+ ## CLI commands used by this skill
20
+
21
+ - `agentation start` / `agentation stop` / `agentation status`
22
+ - `agentation projects --json`
23
+ - `agentation pending <project-id> --json`
24
+ - `agentation watch <project-id> --json`
25
+ - `agentation ack <id>`
26
+ - `agentation resolve <id> --summary "..."`
27
+ - `agentation reply <id> --message "..."`
28
+ - `agentation dismiss <id> --reason "..."`
29
+
30
+ ## Preflight (required)
31
+
32
+ ### 1) Ensure the Agentation CLI is available
33
+
34
+ ```bash
35
+ command -v agentation >/dev/null || { echo "agentation CLI not found on PATH"; exit 1; }
36
+ ```
37
+
38
+ If this fails, install or build the Agentation CLI first and ensure the `agentation` binary is on `PATH`.
39
+
40
+ ### 2) Check whether the Agentation stack is already running
41
+
42
+ ```bash
43
+ agentation status
44
+ ```
45
+
46
+ Then verify API reachability (default `http://127.0.0.1:4747`):
47
+
48
+ ```bash
49
+ agentation projects --json >/dev/null
50
+ ```
51
+
52
+ If not running or unreachable, **start it before doing anything else**:
53
+
54
+ ```bash
55
+ agentation start --background
56
+ # or foreground during debugging
57
+ agentation start --foreground
58
+ ```
59
+
60
+ Re-check after start:
61
+
62
+ ```bash
63
+ agentation status
64
+ agentation projects --json >/dev/null
65
+ ```
66
+
67
+ If you only want the HTTP API without router for this run:
68
+
69
+ ```bash
70
+ AGENTATION_ROUTER_ADDR=0 agentation start --background
71
+ ```
72
+
73
+ ### 3) Determine the project ID and fetch pending work
74
+
75
+ If the skill was invoked with a user argument (for example `/skill:agentation-fix-loop project-alpha`), use that `<project-id>` directly and skip discovery.
76
+
77
+ Otherwise, quickly extract project IDs from your app code:
78
+
79
+ ```bash
80
+ rg -n --glob '*.{tsx,ts,jsx,js}' '<Agentation[^>]*projectId='
81
+ ```
82
+
83
+ If you want to extract a literal string value quickly (when set as `projectId="..."` or `projectId='...'`):
84
+
85
+ ```bash
86
+ rg -o --no-filename --glob '*.{tsx,ts,jsx,js}' "projectId=(?:\"[^\"]+\"|'[^']+')" \
87
+ | head -n1 \
88
+ | sed -E "s/projectId=(\"([^\"]+)\"|'([^']+)')/\2\3/"
89
+ ```
90
+
91
+ Then fetch the initial batch:
92
+
93
+ ```bash
94
+ agentation pending <project-id> --json
95
+ ```
96
+
97
+ Process that batch first, then enter watch mode.
98
+
99
+ ### CLI behavior notes
100
+
101
+ - `agentation start` manages server + router under one process by default.
102
+ - One running Agentation stack is enough for multiple local projects/sessions.
103
+ - Do not start extra instances unless intentionally isolating ports/storage.
104
+
105
+ ## Behavior
106
+
107
+ 1. Call:
108
+
109
+ ```bash
110
+ agentation watch <project-id> --timeout 300 --batch-window 10 --json
111
+ ```
112
+
113
+ 2. For each annotation in the returned batch:
114
+
115
+ a. **Acknowledge**
116
+
117
+ ```bash
118
+ agentation ack <annotation-id>
119
+ ```
120
+
121
+ b. **Understand**
122
+ - Read annotation text (`comment`)
123
+ - Read target context (`element`, `elementPath`, `url`, `nearbyText`, `reactComponents`)
124
+ - Map to likely source files before editing
125
+
126
+ c. **Fix**
127
+ - Make the code change requested by the annotation
128
+ - Keep changes minimal and aligned with project conventions
129
+
130
+ d. **Resolve**
131
+
132
+ ```bash
133
+ agentation resolve <annotation-id> --summary "<short file + change summary>"
134
+ ```
135
+
136
+ 3. After processing the batch, loop back to step 1.
137
+
138
+ 4. Stop when:
139
+ - user says stop, or
140
+ - watch times out repeatedly with no new work.
141
+
142
+ ## Rules
143
+
144
+ - Always acknowledge before starting work.
145
+ - Keep resolve summaries concise (1–2 sentences, mention file(s) + result).
146
+ - If unclear, ask via thread reply instead of guessing:
147
+
148
+ ```bash
149
+ agentation reply <annotation-id> --message "I need clarification on ..."
150
+ ```
151
+
152
+ - If not actionable, dismiss with reason:
153
+
154
+ ```bash
155
+ agentation dismiss <annotation-id> --reason "Not actionable because ..."
156
+ ```
157
+
158
+ - Process annotations in received order.
159
+ - Only resolve once the requested change is implemented.
160
+
161
+ ## Required project-scoped loop
162
+
163
+ Use `<project-id>` as the first argument for all pending/watch commands. Use timeout of at least 300s:
164
+
165
+ ```bash
166
+ agentation projects --json
167
+ agentation pending <project-id> --json
168
+ agentation watch <project-id> --timeout 300 --batch-window 10 --json
169
+ ```
170
+
171
+ ## Loop template
172
+
173
+ ```text
174
+ Round 1:
175
+ agentation pending <project-id> --json
176
+ -> process all returned annotations
177
+
178
+ Round 2:
179
+ agentation watch <project-id> --timeout 300 --batch-window 10 --json
180
+ -> got 2 annotations
181
+ -> ack #1, fix, resolve #1
182
+ -> ack #2, reply (needs clarification)
183
+
184
+ Round 3:
185
+ agentation watch <project-id> --timeout 300 --batch-window 10 --json
186
+ -> got 1 annotation (clarification follow-up)
187
+ -> ack, fix, resolve
188
+
189
+ Round 4:
190
+ agentation watch <project-id> --timeout 300 --batch-window 10 --json
191
+ -> timeout true, no annotations
192
+ -> exit (or continue if user requested persistent watch mode)
193
+ ```
194
+
195
+ ## Troubleshooting
196
+
197
+ - `agentation pending` fails: Agentation is not running, base URL is wrong (`agentation start --background`), or `<project-id>` is missing.
198
+ - If using non-default server URL, pass `--base-url` or set `AGENTATION_BASE_URL`.
199
+ - If frontend keeps creating new sessions unexpectedly, verify localStorage/session behavior in the host app or Storybook setup.