ai-foreman 1.7.10 → 1.7.11
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 +3 -3
- package/dist/activity.d.ts +6 -1
- package/dist/activity.js +39 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,9 +15,9 @@ Loop Claude Code or Codex through tickets.
|
|
|
15
15
|
- **Rich ticket system** — tickets carry acceptance criteria, required tests, dependencies, priority, size, and risk level. The agent reads the full context before starting each step.
|
|
16
16
|
- **Future work tracking** — when the builder discovers out-of-scope work during a run, `ai-foreman tickets discover` captures it without derailing the current ticket. Discovered items live in a separate inbox until you promote them.
|
|
17
17
|
- **QA that actually gates** — QA runs after every completed ticket and checks code quality, test passing, and regression protection. The ticket only closes after QA passes. QA turns do not count against `--steps`.
|
|
18
|
-
- **Visible long-running work** —
|
|
18
|
+
- **Visible long-running work** — semantic activity rows identify provider, phase, tools, retries, and quiet periods; cursor-capable terminals update changing counters in place, while record-oriented terminals coalesce them.
|
|
19
19
|
|
|
20
|
-
Provider-managed retries are reported immediately as durable lines. A quiet-provider warning appears after 60 seconds without a signal, but Foreman does not add retries, abort the provider, or change provider retry limits. Non-TTY output receives a heartbeat every 30 seconds while work remains pending.
|
|
20
|
+
Provider-managed retries are reported immediately as durable lines. A quiet-provider warning appears after 60 seconds without a signal, but Foreman does not add retries, abort the provider, or change provider retry limits. Non-TTY output receives a heartbeat every 30 seconds while work remains pending. TTY rendering defaults to automatic detection: Codex, CI, and `TERM=dumb` use newline records and suppress spinner- or number-only repeats; other terminals redraw those changes in place and retain a row when the textual status changes. Use `RAFI_ACTIVITY_RENDER_MODE=auto|cursor|records` to override detection.
|
|
21
21
|
|
|
22
22
|
## Install
|
|
23
23
|
|
|
@@ -146,7 +146,7 @@ Saved `tickets.build` defaults in `rafi-config.yaml` can enable branch-per-ticke
|
|
|
146
146
|
|
|
147
147
|
With the saved `current` strategy, Foreman works in the active branch while the user owns Git. It may edit, test, run QA, and update tracker/recovery state, but all branch/worktree, commit, push, merge, rebase, and review lifecycle commands are fenced. An unexpected active ref or worktree change pauses the run. Explicit isolated flags are reported as run overrides.
|
|
148
148
|
|
|
149
|
-
The
|
|
149
|
+
The current terminal status includes the role/provider, activity, truthful context state, successful compaction count, and handoff generation. At the Builder threshold, Foreman settles the in-flight action, verifies native compaction plus a fresh provider usage sample, and resumes the frozen action. The configured maximum defaults to ten successful compactions per location-scoped provider session; the next crossing uses a validated fresh handoff instead of exceeding it. Disposable QA snapshots never reuse a prior QA conversation in a new `/tmp/rafi-qa-*` worktree; they transfer cumulative state through an accepted handoff.
|
|
150
150
|
|
|
151
151
|
### GitHub PR Failure Recovery
|
|
152
152
|
|
package/dist/activity.d.ts
CHANGED
|
@@ -11,7 +11,10 @@ export interface ActivityReporterOptions {
|
|
|
11
11
|
tickMs?: number;
|
|
12
12
|
heartbeatMs?: number;
|
|
13
13
|
quietWarningMs?: number;
|
|
14
|
+
/** Override automatic TTY capability detection for tests or embedding hosts. */
|
|
15
|
+
ttyMode?: ActivityTtyMode;
|
|
14
16
|
}
|
|
17
|
+
export type ActivityTtyMode = "auto" | "cursor" | "records";
|
|
15
18
|
/** One process-local live view shared by nested RAFI operations. */
|
|
16
19
|
export declare class ActivityReporter {
|
|
17
20
|
readonly command: string;
|
|
@@ -21,6 +24,7 @@ export declare class ActivityReporter {
|
|
|
21
24
|
private readonly tickMs;
|
|
22
25
|
private readonly heartbeatMs;
|
|
23
26
|
private readonly quietWarningMs;
|
|
27
|
+
private readonly ttyMode;
|
|
24
28
|
private readonly commandStartedAt;
|
|
25
29
|
private readonly phases;
|
|
26
30
|
private nextPhaseId;
|
|
@@ -33,6 +37,7 @@ export declare class ActivityReporter {
|
|
|
33
37
|
private quietWarningPrinted;
|
|
34
38
|
private lastHeartbeatAt;
|
|
35
39
|
private lineVisible;
|
|
40
|
+
private lastSemanticKey?;
|
|
36
41
|
private paused;
|
|
37
42
|
private agentStatusLine?;
|
|
38
43
|
constructor(command: string, options?: ActivityReporterOptions);
|
|
@@ -45,7 +50,7 @@ export declare class ActivityReporter {
|
|
|
45
50
|
pulse(detail?: string): void;
|
|
46
51
|
note(message: string): void;
|
|
47
52
|
writePersistent(text: string): void;
|
|
48
|
-
/** Keep role/provider/context state
|
|
53
|
+
/** Keep role/provider/context state attached to the current activity status. */
|
|
49
54
|
setAgentStatus(line: string | undefined): void;
|
|
50
55
|
pause(): () => void;
|
|
51
56
|
dispose(): void;
|
package/dist/activity.js
CHANGED
|
@@ -10,6 +10,7 @@ export class ActivityReporter {
|
|
|
10
10
|
tickMs;
|
|
11
11
|
heartbeatMs;
|
|
12
12
|
quietWarningMs;
|
|
13
|
+
ttyMode;
|
|
13
14
|
commandStartedAt;
|
|
14
15
|
phases = new Map();
|
|
15
16
|
nextPhaseId = 1;
|
|
@@ -22,6 +23,7 @@ export class ActivityReporter {
|
|
|
22
23
|
quietWarningPrinted = false;
|
|
23
24
|
lastHeartbeatAt = 0;
|
|
24
25
|
lineVisible = false;
|
|
26
|
+
lastSemanticKey;
|
|
25
27
|
paused = 0;
|
|
26
28
|
agentStatusLine;
|
|
27
29
|
constructor(command, options = {}) {
|
|
@@ -35,6 +37,7 @@ export class ActivityReporter {
|
|
|
35
37
|
this.tickMs = options.tickMs ?? 1_000;
|
|
36
38
|
this.heartbeatMs = options.heartbeatMs ?? 30_000;
|
|
37
39
|
this.quietWarningMs = options.quietWarningMs ?? 60_000;
|
|
40
|
+
this.ttyMode = resolveTtyMode(options.ttyMode);
|
|
38
41
|
this.commandStartedAt = this.now();
|
|
39
42
|
}
|
|
40
43
|
begin(label) {
|
|
@@ -91,7 +94,7 @@ export class ActivityReporter {
|
|
|
91
94
|
this.output.write("\n");
|
|
92
95
|
this.render(true);
|
|
93
96
|
}
|
|
94
|
-
/** Keep role/provider/context state
|
|
97
|
+
/** Keep role/provider/context state attached to the current activity status. */
|
|
95
98
|
setAgentStatus(line) {
|
|
96
99
|
this.agentStatusLine = line ? clean(line, 500) : undefined;
|
|
97
100
|
if (this.agentStatusLine && !this.timer) {
|
|
@@ -130,7 +133,6 @@ export class ActivityReporter {
|
|
|
130
133
|
}
|
|
131
134
|
idle() {
|
|
132
135
|
if (this.agentStatusLine) {
|
|
133
|
-
this.clearLine();
|
|
134
136
|
this.detail = undefined;
|
|
135
137
|
this.provider = undefined;
|
|
136
138
|
this.model = undefined;
|
|
@@ -145,6 +147,7 @@ export class ActivityReporter {
|
|
|
145
147
|
this.provider = undefined;
|
|
146
148
|
this.model = undefined;
|
|
147
149
|
this.agentStatusLine = undefined;
|
|
150
|
+
this.lastSemanticKey = undefined;
|
|
148
151
|
}
|
|
149
152
|
currentPhase() {
|
|
150
153
|
return [...this.phases.values()].at(-1);
|
|
@@ -168,8 +171,21 @@ export class ActivityReporter {
|
|
|
168
171
|
const body = [phase?.label, provider || undefined, phase ? quiet : undefined, this.agentStatusLine].filter(Boolean).join(" — ");
|
|
169
172
|
if (this.output.isTTY) {
|
|
170
173
|
const line = `${FRAMES[this.frame++ % FRAMES.length]} RAFI working: ${body} (${formatDuration(now - this.commandStartedAt)})`;
|
|
174
|
+
const semanticKey = semanticStatusKey(body);
|
|
175
|
+
if (this.ttyMode === "records") {
|
|
176
|
+
if (semanticKey === this.lastSemanticKey)
|
|
177
|
+
return;
|
|
178
|
+
this.output.write(`${line}\n`);
|
|
179
|
+
this.lastSemanticKey = semanticKey;
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (this.lineVisible && semanticKey !== this.lastSemanticKey) {
|
|
183
|
+
this.output.write("\n");
|
|
184
|
+
this.lineVisible = false;
|
|
185
|
+
}
|
|
171
186
|
this.output.write(`\r\x1b[2K${line}`);
|
|
172
187
|
this.lineVisible = true;
|
|
188
|
+
this.lastSemanticKey = semanticKey;
|
|
173
189
|
return;
|
|
174
190
|
}
|
|
175
191
|
if (force || now - this.lastHeartbeatAt >= this.heartbeatMs) {
|
|
@@ -260,6 +276,27 @@ function briefInput(input) {
|
|
|
260
276
|
function clean(value, maximum) {
|
|
261
277
|
return value.replace(/[\r\n\t]+/g, " ").replace(/\s+/g, " ").trim().slice(0, maximum);
|
|
262
278
|
}
|
|
279
|
+
function semanticStatusKey(body) {
|
|
280
|
+
return body.replace(/\p{Number}+(?:[.,]\p{Number}+)*/gu, "#");
|
|
281
|
+
}
|
|
282
|
+
function resolveTtyMode(requested) {
|
|
283
|
+
const configured = requested ?? configuredTtyMode(process.env.RAFI_ACTIVITY_RENDER_MODE);
|
|
284
|
+
if (configured === "cursor" || configured === "records")
|
|
285
|
+
return configured;
|
|
286
|
+
return truthyEnvironmentValue(process.env.CODEX_CI)
|
|
287
|
+
|| truthyEnvironmentValue(process.env.CI)
|
|
288
|
+
|| process.env.TERM?.toLowerCase() === "dumb"
|
|
289
|
+
? "records"
|
|
290
|
+
: "cursor";
|
|
291
|
+
}
|
|
292
|
+
function configuredTtyMode(value) {
|
|
293
|
+
return value === "cursor" || value === "records" || value === "auto" ? value : "auto";
|
|
294
|
+
}
|
|
295
|
+
function truthyEnvironmentValue(value) {
|
|
296
|
+
if (value === undefined)
|
|
297
|
+
return false;
|
|
298
|
+
return !["", "0", "false"].includes(value.trim().toLowerCase());
|
|
299
|
+
}
|
|
263
300
|
function formatDuration(milliseconds) {
|
|
264
301
|
const seconds = Math.max(0, Math.floor(milliseconds / 1_000));
|
|
265
302
|
if (seconds < 60)
|
package/package.json
CHANGED