agentic-retro 0.1.3 → 0.1.4
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/README.md +27 -5
- package/cli/agentic-retro.js +5 -2
- package/cli/lib/notice.js +8 -5
- package/cli/lib/pi-extension.ts +2 -2
- package/cli/lib/project.js +42 -0
- package/cli/lib/queue.js +4 -16
- package/cli/lib/setup.js +1 -1
- package/package.json +1 -1
- package/skills/setup/SKILL.md +9 -1
package/README.md
CHANGED
|
@@ -6,9 +6,11 @@ agentic-retro ("agentic-retrospective") learns from your finished coding session
|
|
|
6
6
|
|
|
7
7
|
```mermaid
|
|
8
8
|
flowchart TD
|
|
9
|
-
A["Session ends<br/>(hook in each harness)"] -->
|
|
9
|
+
A["Session ends<br/>(hook in each harness)"] --> P{"Project enabled?"}
|
|
10
|
+
P -->|no| Z["Stop"]
|
|
11
|
+
P -->|yes| B["CLI adds session to queue"]
|
|
10
12
|
B --> C{"5 sessions queued for this repo?"}
|
|
11
|
-
C -->|no| Z
|
|
13
|
+
C -->|no| Z
|
|
12
14
|
C -->|yes| D["CLI writes digests and metrics,<br/>and updates past proposal outcomes"]
|
|
13
15
|
D --> E["Runner starts one lead agent;<br/>it runs one subagent per ticked category<br/>(Claude, Codex, Pi, or herdr pane)"]
|
|
14
16
|
E --> F["Subagents write proposals,<br/>skipping anything rejected or expired before"]
|
|
@@ -17,7 +19,7 @@ flowchart TD
|
|
|
17
19
|
H --> I["User reviews proposals"]
|
|
18
20
|
```
|
|
19
21
|
|
|
20
|
-
- A session-end hook
|
|
22
|
+
- A session-end hook queues sessions only for explicitly enabled repositories (`agentic-retro enqueue`)
|
|
21
23
|
- Once a repo has 5 queued sessions (configurable), the CLI starts a batch
|
|
22
24
|
- One lead agent runs per batch, and it runs one subagent per ticked category: code clarity, agent setup, repo changes
|
|
23
25
|
- Agents write proposals as a PR (one per repo) or a report file, per your setup choice
|
|
@@ -73,6 +75,23 @@ Both write the same `~/.agentic-retro/config.json`:
|
|
|
73
75
|
- which categories run, and the output mode (PR or report)
|
|
74
76
|
- where agent-setup proposals land
|
|
75
77
|
|
|
78
|
+
Enable collection separately in each repository you want reviewed:
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
agentic-retro project enable
|
|
82
|
+
agentic-retro project status
|
|
83
|
+
# Stop collection and startup notices for this repository:
|
|
84
|
+
agentic-retro project disable
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
- Every repository starts disabled, including repositories used before this update
|
|
88
|
+
- Run these commands from any worktree or subdirectory, or pass `--cwd <dir>`
|
|
89
|
+
- All worktrees share the setting, including worktrees elsewhere on the machine
|
|
90
|
+
- Git stores `agentic-retro.enabled` in its shared local configuration, outside tracked files
|
|
91
|
+
- Separate clones require separate opt-in
|
|
92
|
+
- Disabling retains queued sessions but excludes them from future batches until re-enabled
|
|
93
|
+
- Disabling does not stop an already-running batch
|
|
94
|
+
|
|
76
95
|
Using Pi? Run `agentic-retro install-pi` once. Pi has no hook config, so this extension queues finished Pi sessions and shows the notice.
|
|
77
96
|
|
|
78
97
|
Using Codex? It asks you to trust the new session-end hook the first time a session ends after install.
|
|
@@ -88,7 +107,8 @@ Using Codex? It asks you to trust the new session-end hook the first time a sess
|
|
|
88
107
|
|
|
89
108
|
- `agentic-retro about` – print a short intro to what agentic-retro does
|
|
90
109
|
- `agentic-retro setup` – interactive config, the human alternative to the setup skill
|
|
91
|
-
- `agentic-retro config list | get <key> | set <key> <value>` – read or write one config key
|
|
110
|
+
- `agentic-retro config list | get <key> | set <key> <value>` – read or write one global config key
|
|
111
|
+
- `agentic-retro project enable | disable | status [--cwd <dir>]` – control collection and notices for one repository
|
|
92
112
|
- `agentic-retro enqueue` – add a finished session to the queue (hooks call this)
|
|
93
113
|
- `agentic-retro propose` – record a new proposal (batch agents call this)
|
|
94
114
|
- `agentic-retro link <id>` – attach a PR and commit to a proposal
|
|
@@ -101,4 +121,6 @@ Using Codex? It asks you to trust the new session-end hook the first time a sess
|
|
|
101
121
|
|
|
102
122
|
## State
|
|
103
123
|
|
|
104
|
-
|
|
124
|
+
- Runtime state lives under `~/.agentic-retro/` (override with `AGENTIC_RETRO_HOME`): config, queue, ledger, metrics, batch digests, and reports
|
|
125
|
+
- Repository activation lives in Git's shared local configuration and never enters the tracked checkout
|
|
126
|
+
- Keep runtime state outside repositories; Git ignores its own metadata automatically
|
package/cli/agentic-retro.js
CHANGED
|
@@ -11,17 +11,19 @@ import { notice } from './lib/notice.js';
|
|
|
11
11
|
import { stats } from './lib/stats.js';
|
|
12
12
|
import { installPi } from './lib/pi.js';
|
|
13
13
|
import { INTRO } from './lib/intro.js';
|
|
14
|
+
import { project } from './lib/project.js';
|
|
14
15
|
|
|
15
16
|
const USAGE = `usage: agentic-retro <command>
|
|
16
17
|
about
|
|
17
18
|
setup
|
|
19
|
+
project enable | disable | status [--cwd <dir>]
|
|
18
20
|
config list | get <key> | set <key> <value>
|
|
19
21
|
enqueue [--harness <h>] [--session <id> --transcript <path> --cwd <dir>] (or hook JSON on stdin)
|
|
20
22
|
propose --repo <path> --category <c> [--risk low|high] --title <t> [--pr <pr> --commit <msg>]
|
|
21
23
|
mark <id> <accepted|rejected>
|
|
22
24
|
link <id> [--pr <url>] [--commit <sha>]
|
|
23
25
|
ledger [--status <s>[,<s>...]]
|
|
24
|
-
notice [--json]
|
|
26
|
+
notice [--json] [--cwd <dir>]
|
|
25
27
|
stats [--json]
|
|
26
28
|
sync
|
|
27
29
|
install-pi`;
|
|
@@ -69,13 +71,14 @@ async function main() {
|
|
|
69
71
|
|
|
70
72
|
if (command === 'setup') return setup();
|
|
71
73
|
if (command === 'config') return config(positionals);
|
|
74
|
+
if (command === 'project') return project(positionals[0], flags.cwd);
|
|
72
75
|
if (command === 'enqueue') return enqueueCommand(flags);
|
|
73
76
|
if (command === 'propose') return console.log(propose(flags));
|
|
74
77
|
if (command === 'mark') return mark(...positionals);
|
|
75
78
|
if (command === 'link') return link(positionals[0], flags);
|
|
76
79
|
if (command === 'ledger') return listProposals(flags.status).forEach((p) => console.log(formatProposal(p)));
|
|
77
80
|
if (command === 'sync') return syncOutcomes();
|
|
78
|
-
if (command === 'notice') return notice(flags);
|
|
81
|
+
if (command === 'notice') return notice({ ...flags, cwd: flags.cwd ?? readHook().cwd ?? process.cwd() });
|
|
79
82
|
if (command === 'stats') return stats(flags);
|
|
80
83
|
if (command === 'install-pi') return installPi();
|
|
81
84
|
if (command === 'about') return process.stdout.write(INTRO);
|
package/cli/lib/notice.js
CHANGED
|
@@ -3,6 +3,7 @@ import { existsSync } from 'node:fs';
|
|
|
3
3
|
import { stateDir, statePath, readJson, writeJson } from './paths.js';
|
|
4
4
|
import { loadConfig } from './config.js';
|
|
5
5
|
import { listProposals } from './ledger.js';
|
|
6
|
+
import { projectEnabled, repoOf } from './project.js';
|
|
6
7
|
|
|
7
8
|
function linkFor(proposal, output) {
|
|
8
9
|
return output === 'report' ? statePath('reports', `${proposal.batch}.md`) : proposal.pr;
|
|
@@ -10,13 +11,15 @@ function linkFor(proposal, output) {
|
|
|
10
11
|
|
|
11
12
|
// notice.json remembers which open proposals were already shown with their link.
|
|
12
13
|
// In PR mode a proposal without a PR stays new, so a later notice can name its PR.
|
|
13
|
-
export function noticeLine() {
|
|
14
|
-
if (process.env.AGENTIC_RETRO_RUN === '1' || !existsSync(stateDir())) return '';
|
|
14
|
+
export function noticeLine(cwd = process.cwd()) {
|
|
15
|
+
if (process.env.AGENTIC_RETRO_RUN === '1' || !projectEnabled(cwd) || !existsSync(stateDir())) return '';
|
|
16
|
+
const repo = repoOf(cwd);
|
|
15
17
|
|
|
16
18
|
const { output } = loadConfig();
|
|
17
19
|
const { noticed = [] } = readJson('notice.json', {});
|
|
18
20
|
const open = listProposals('open');
|
|
19
|
-
const fresh = open.filter((p) => !noticed.includes(p.id))
|
|
21
|
+
const fresh = open.filter((p) => !noticed.includes(p.id)).filter((p) => repoOf(p.repo) === repo ||
|
|
22
|
+
readJson(`batches/${p.batch}/batch.json`, { sessions: [] }).sessions.some((s) => repoOf(s.cwd, s.repo) === repo));
|
|
20
23
|
if (!fresh.length) return '';
|
|
21
24
|
|
|
22
25
|
const shown = open.filter((p) => noticed.includes(p.id) || (fresh.includes(p) && linkFor(p, output)));
|
|
@@ -28,9 +31,9 @@ export function noticeLine() {
|
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
// A session-start hook must never break the session, so any failure prints nothing.
|
|
31
|
-
export function notice({ json }) {
|
|
34
|
+
export function notice({ json, cwd }) {
|
|
32
35
|
let line = '';
|
|
33
|
-
try { line = noticeLine(); } catch {}
|
|
36
|
+
try { line = noticeLine(cwd); } catch {}
|
|
34
37
|
if (!line) return;
|
|
35
38
|
console.log(json ? JSON.stringify({ systemMessage: line }) : line);
|
|
36
39
|
}
|
package/cli/lib/pi-extension.ts
CHANGED
|
@@ -78,7 +78,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
78
78
|
pi.registerCommand("agentic-retro", {
|
|
79
79
|
description: "Run the package-local agentic-retro CLI",
|
|
80
80
|
handler: async (args, ctx) => {
|
|
81
|
-
execFile(process.execPath, [CLI, ...argv(args)], (error, stdout, stderr) => {
|
|
81
|
+
execFile(process.execPath, [CLI, ...argv(args)], { cwd: ctx.cwd }, (error, stdout, stderr) => {
|
|
82
82
|
const output = (stdout || stderr).trim();
|
|
83
83
|
if (output) ctx.ui.notify(output, error ? "error" : "info");
|
|
84
84
|
});
|
|
@@ -121,7 +121,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
121
121
|
});
|
|
122
122
|
|
|
123
123
|
pi.on("session_start", (_event, ctx) => {
|
|
124
|
-
execFile(process.execPath, [CLI, "notice"], (error, stdout) => {
|
|
124
|
+
execFile(process.execPath, [CLI, "notice", "--cwd", ctx.cwd], (error, stdout) => {
|
|
125
125
|
const line = stdout?.trim();
|
|
126
126
|
if (!error && line) ctx.ui.notify(line, "info");
|
|
127
127
|
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Repository identity and opt-in live in Git's shared metadata, not in a checkout.
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
|
|
5
|
+
const options = { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] };
|
|
6
|
+
const KEY = 'agentic-retro.enabled';
|
|
7
|
+
|
|
8
|
+
function commonDir(cwd) {
|
|
9
|
+
const path = execFileSync('git', ['-C', cwd, 'rev-parse', '--git-common-dir'], options).replace(/\n$/, '');
|
|
10
|
+
return resolve(cwd, path);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function repoOf(cwd, fallback = cwd) {
|
|
14
|
+
try {
|
|
15
|
+
// Git lists the shared identity first: the main checkout or its separate metadata directory.
|
|
16
|
+
const worktrees = execFileSync('git', ['--git-dir', commonDir(cwd), 'worktree', 'list', '--porcelain', '-z'], options);
|
|
17
|
+
const main = worktrees.split('\0').find((field) => field.startsWith('worktree '));
|
|
18
|
+
return main ? main.slice('worktree '.length) : fallback;
|
|
19
|
+
} catch {
|
|
20
|
+
return fallback;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function projectEnabled(cwd) {
|
|
25
|
+
if (!cwd) return false;
|
|
26
|
+
try {
|
|
27
|
+
// Read only the shared config: global and per-worktree overrides cannot enable a project.
|
|
28
|
+
return execFileSync('git', ['config', '--file', join(commonDir(cwd), 'config'), '--bool', '--get', KEY], options).trim() === 'true';
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function project(action, cwd = process.cwd()) {
|
|
35
|
+
if (!['enable', 'disable', 'status'].includes(action)) throw new Error('usage: agentic-retro project enable|disable|status [--cwd <dir>]');
|
|
36
|
+
if (action !== 'status') {
|
|
37
|
+
let dir;
|
|
38
|
+
try { dir = commonDir(cwd); } catch { throw new Error('project enable/disable needs a Git repository'); }
|
|
39
|
+
execFileSync('git', ['config', '--file', join(dir, 'config'), KEY, String(action === 'enable')], options);
|
|
40
|
+
}
|
|
41
|
+
console.log(projectEnabled(cwd) ? 'enabled' : 'disabled');
|
|
42
|
+
}
|
package/cli/lib/queue.js
CHANGED
|
@@ -1,25 +1,12 @@
|
|
|
1
1
|
// The queue: finished sessions wait here until their group fills a batch.
|
|
2
|
-
import { execFileSync } from 'node:child_process';
|
|
3
2
|
import { createReadStream, readdirSync } from 'node:fs';
|
|
4
3
|
import { homedir } from 'node:os';
|
|
5
|
-
import { join, basename
|
|
4
|
+
import { join, basename } from 'node:path';
|
|
6
5
|
import { createInterface } from 'node:readline';
|
|
7
6
|
import { readJson, writeJson } from './paths.js';
|
|
8
7
|
import { loadConfig } from './config.js';
|
|
9
8
|
import { startBatch } from './runner.js';
|
|
10
|
-
|
|
11
|
-
function repoOf(cwd, fallback = cwd) {
|
|
12
|
-
try {
|
|
13
|
-
const options = { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] };
|
|
14
|
-
const commonDir = execFileSync('git', ['-C', cwd, 'rev-parse', '--git-common-dir'], options).replace(/\n$/, '');
|
|
15
|
-
// Git lists the shared identity first: the main checkout or its separate metadata directory.
|
|
16
|
-
const worktrees = execFileSync('git', ['--git-dir', resolve(cwd, commonDir), 'worktree', 'list', '--porcelain', '-z'], options);
|
|
17
|
-
const main = worktrees.split('\0').find((field) => field.startsWith('worktree '));
|
|
18
|
-
return main ? main.slice('worktree '.length) : fallback;
|
|
19
|
-
} catch {
|
|
20
|
-
return fallback;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
9
|
+
import { projectEnabled, repoOf } from './project.js';
|
|
23
10
|
|
|
24
11
|
// Codex sometimes ends a session with a null transcript_path, so look for its rollout file.
|
|
25
12
|
function findCodexRollout(sessionId) {
|
|
@@ -60,6 +47,7 @@ async function isPiChildTranscript(transcript) {
|
|
|
60
47
|
|
|
61
48
|
export async function enqueue({ harness, sessionId, transcript, cwd }) {
|
|
62
49
|
if (process.env.AGENTIC_RETRO_RUN === '1') return; // batch agents never queue themselves
|
|
50
|
+
if (!projectEnabled(cwd)) return;
|
|
63
51
|
transcript ||= findCodexRollout(sessionId) ?? null;
|
|
64
52
|
harness ||= harnessOf(transcript);
|
|
65
53
|
if (!harness || !sessionId || !cwd) throw new Error('enqueue needs a harness, session, and cwd');
|
|
@@ -72,7 +60,7 @@ export async function enqueue({ harness, sessionId, transcript, cwd }) {
|
|
|
72
60
|
|
|
73
61
|
const { batch } = loadConfig();
|
|
74
62
|
const sameGroup = (s) => batch.group === 'global' || s.repo === entry.repo;
|
|
75
|
-
const group = queue.filter(sameGroup);
|
|
63
|
+
const group = queue.filter((s) => sameGroup(s) && projectEnabled(s.repo));
|
|
76
64
|
if (group.length < batch.size) return writeJson('queue.json', queue);
|
|
77
65
|
|
|
78
66
|
// Save first and remove after, so a failed hand-off leaves the sessions queued.
|
package/cli/lib/setup.js
CHANGED
package/package.json
CHANGED
package/skills/setup/SKILL.md
CHANGED
|
@@ -25,7 +25,7 @@ Offer the presets below for each question, and let the user type a different ans
|
|
|
25
25
|
1. **Batch size** – sessions per batch, default 5
|
|
26
26
|
2. **Grouping** – `repo` (default) or `global`
|
|
27
27
|
3. **Runner harness** – `claude`, `codex`, or `pi`; add `herdr` to the choices only if `command -v herdr` finds it
|
|
28
|
-
4. **Model** – `opus` or `default`; `opus` applies to Claude, Codex and Pi use their
|
|
28
|
+
4. **Model** – `opus` or `default`; `opus` applies to Claude, Codex and Pi use their default unless given a model name they understand (for example `o3`)
|
|
29
29
|
5. **Runner permissions** – how much the batch agent can do without asking:
|
|
30
30
|
- `auto` (default) – the harness's own auto mode
|
|
31
31
|
- `scoped` – file edits, plus only `git`, `gh`, and `agentic-retro` commands
|
|
@@ -67,6 +67,14 @@ Never edit `config.json` by hand. `config set` is the only path that keeps the s
|
|
|
67
67
|
|
|
68
68
|
Run `command -v pi`. If it finds Pi, ask whether to install the agentic-retro extension for it. On yes, run `agentic-retro install-pi`.
|
|
69
69
|
|
|
70
|
+
## Enable this repository
|
|
71
|
+
|
|
72
|
+
Every repository starts disabled. Ask whether the user wants to enable collection in the current repository.
|
|
73
|
+
|
|
74
|
+
On yes, run `agentic-retro project enable` from that repository. All its worktrees share this untracked Git setting.
|
|
75
|
+
|
|
76
|
+
On no, leave the repository disabled. Global configuration alone does not enable collection.
|
|
77
|
+
|
|
70
78
|
## Finish
|
|
71
79
|
|
|
72
80
|
Run `agentic-retro config list` and show the user the result.
|