@hank-warren/pi-loop 1.1.0 → 1.2.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 CHANGED
@@ -1,5 +1,33 @@
1
1
  # @hank-warren/pi-loop
2
2
 
3
+ ## 1.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 1035138: Ship the craft documents by injected path instead of as skills.
8
+
9
+ The `pi-loop` and `pi-plan-mode` skills are gone. Their bodies now ship as
10
+ `docs/loop-craft.md` and `docs/plan-craft.md`, and the mode prompts inject the
11
+ file's absolute path (resolved from the installed package) at the moments the
12
+ guidance matters: while a loop is being drafted or completed, and while Plan
13
+ Mode is active. A skill's description line sits in every system prompt of every
14
+ session with the package loaded; across ~220 sessions after these two shipped,
15
+ every read of either file was triggered by the mode's own prompt and never by
16
+ the description, so the line was a tax on the ~95% of sessions that never
17
+ entered the mode. Same document, read at the same moments, at zero cost outside
18
+ them. Hosts that referenced the skills by name in settings should drop those
19
+ entries.
20
+
21
+ - 1035138: Deprecated in favour of `@hank-warren/pi-orchestrator`.
22
+
23
+ pi-loop is a pacemaker for one session that cannot be trusted to pace itself. The
24
+ `pi-orchestrator` skill is a supervising pi session watching real pi sessions in
25
+ Herdr panes — reading their state, answering their prompts within policy,
26
+ steering, and verifying their claims — which does by judgment what the loop
27
+ engine did by pacing and gates. This package stays published and continues to
28
+ work; it is no longer loaded by the git install of the `pi-extensions`
29
+ repository and receives no new features. The README opens with the notice.
30
+
3
31
  ## 1.1.0
4
32
 
5
33
  ### Minor Changes
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # pi-loop — long-running work for the Pi coding agent
2
2
 
3
+ > **Deprecated.** pi-loop is no longer loaded by the git install of this repository and receives no new features. Its role — keeping autonomous work moving and gating completion on evidence — has moved to the [`pi-orchestrator`](../pi-orchestrator) skill package: a supervising pi session watching real pi sessions in Herdr panes, reading their state, answering their prompts within policy, steering them, and verifying their claims. A loop is a pacemaker for one session that cannot be trusted to pace itself; a supervisor with fresh context does that by judgment, and can see, attach to, and redirect each child. The package stays on npm and continues to install with `pi install npm:@hank-warren/pi-loop` for anyone who prefers it.
4
+
3
5
  Inspired by Claude Code's `/loop`, adapted to Pi: keep work moving across many turns, and keep long loops coherent across context compaction.
4
6
 
5
7
  A loop is a **pacemaker** that owns its own work: it carries an objective, completion criteria and ground rules, and ends when the model calls `loop_complete` with cited evidence, its expiry arrives, a cap you set is reached, or you stop it. Every loop is planned with you and started from an approval card. **No other extension is required.**
@@ -205,13 +207,13 @@ These were considered and cut, and the reasoning is recorded so they are not sil
205
207
  - **No judge model.** Grading completion with a second model is a larger, more expensive change than the criteria/evidence gate; the gate is the rung that ships.
206
208
  - **No `loop_blocked` tool.** `loop_wait` covers a real external dependency, and the no-progress breaker covers an impasse the model does not recognise as one. A third "I give up" tool mostly gives a model a way to stop early. `compaction.instructions` overrides the built-in template.
207
209
 
208
- ## The companion skill
210
+ ## The loop-craft doc
209
211
 
210
- The package is a hybrid: it ships the extension **and** a `pi-loop` skill (`skills/pi-loop/SKILL.md`), which carries the judgment the engine cannot encode — how an objective becomes falsifiable criteria, what the evidence gate accepts as a citation, when to declare a `loop_wait` instead of polling, what `PROGRESS.md` is worth, and when the work belongs in no loop at all.
212
+ The engine cannot encode the judgment that decides whether a loop is worth running — how an objective becomes falsifiable criteria, what the evidence gate accepts as a citation, when to declare a `loop_wait` instead of polling, what `PROGRESS.md` is worth, and when the work belongs in no loop at all. That lives in [`docs/loop-craft.md`](docs/loop-craft.md), which ships with the package.
211
213
 
212
- It is **loaded on demand**: the planning hint and `loop_complete`'s prompt guidelines point at it by name, exactly as `pi-processes` does, and the model reads the body when it judges it needs it. Nothing about the guidance enters a stored loop message or the system append — those bytes are the cache prefix, and `test/bytes.test.ts` fails if any of them so much as mentions a skill.
214
+ It is **loaded on demand**: the planning hint and `loop_complete`'s prompt guidelines name it by absolute path (resolved from the installed package, so it works under any install layout) and the model reads it when it reaches those moments. Nothing about the guidance enters a stored loop message or the system append — those bytes are the cache prefix, and `test/bytes.test.ts` fails if any of them so much as mentions it.
213
215
 
214
- Skill and extension version as one artifact on purpose: a skill describing an engine the installed extension does not have is the coupling failure this repository already learned once.
216
+ It used to be a skill. A skill's description line is in every system prompt, which buys exactly one thing an injected pointer cannot: the model proposing a loop unprompted. Across ~220 sessions after it shipped, every read of the file was triggered by the planning hint or the completion tool, never by the description, and the model never suggested `/loop` on its own — so the line was a tax on every session that never ran a loop (about 95% of them) that bought nothing. A hard path injected only while a loop is being drafted or completed is the same document at zero cost outside those moments.
215
217
 
216
218
  ## Install
217
219
 
@@ -221,6 +223,10 @@ pi install npm:@hank-warren/pi-loop
221
223
 
222
224
  **No other extension is required.** The only sibling state pi-loop reads is [pi-plan-mode](https://www.npmjs.com/package/@hank-warren/pi-plan-mode)'s, fail-open, so a loop never injects into a planning conversation — and it works fine without it.
223
225
 
226
+ ## Changelog
227
+
228
+ See [CHANGELOG.md](CHANGELOG.md) for release history.
229
+
224
230
  ## License
225
231
 
226
232
  MIT
@@ -1,9 +1,3 @@
1
- ---
2
- name: pi-loop
3
- description: Draft loop objectives that become falsifiable criteria, set cadence and ground rules, and complete a loop with evidence the gate accepts. Use while drafting a loop in /loop planning, before calling loop_propose, before assembling loop_complete evidence, and when deciding whether work belongs in a loop at all.
4
- license: MIT
5
- ---
6
-
7
1
  # pi-loop
8
2
 
9
3
  The engine handles pacing, wakes, compaction, and the ledger. It cannot handle the three things that decide whether a loop is worth running: **what end state the objective names**, **what constraints bound the work**, and **what evidence proves it is done**. That is this file.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hank-warren/pi-loop",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Long-running work for Pi: settle-paced loops with a durable ledger, adaptive waits, no-progress breakers, evidence-gated completion, and a task scheduler.",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -29,15 +29,12 @@
29
29
  "pi": {
30
30
  "extensions": [
31
31
  "./index.ts"
32
- ],
33
- "skills": [
34
- "./skills/pi-loop"
35
32
  ]
36
33
  },
37
34
  "files": [
38
35
  "index.ts",
39
36
  "src",
40
- "skills",
37
+ "docs/loop-craft.md",
41
38
  "README.md",
42
39
  "LICENSE",
43
40
  "CHANGELOG.md"
package/src/command.ts CHANGED
@@ -17,7 +17,7 @@
17
17
  * of having exactly one way in, and the menu is one keystroke away.
18
18
  */
19
19
 
20
- export type LoopCommand =
20
+ type LoopCommand =
21
21
  /** Bare `/loop`: open whichever menu the current state calls for. */
22
22
  | { kind: "menu" }
23
23
  /** `/loop <text>`: enter planning and send the text as the first message. */
@@ -24,6 +24,7 @@
24
24
 
25
25
  import { defineTool, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
26
26
  import { Type } from "typebox";
27
+ import { LOOP_CRAFT_DOC } from "./docs.js";
27
28
  import type { LoopController } from "./loop.js";
28
29
  import type { LoopCriterion } from "./ledger.js";
29
30
 
@@ -92,7 +93,7 @@ export function registerLoopCompleteTool(pi: ExtensionAPI, controller: LoopContr
92
93
  "Pass the exact loop_id from the active /loop objective in the system prompt. A mismatched id means the loop changed and the call is refused.",
93
94
  "loop_complete stops scheduled wakeups only. It does not mean an unrelated goal or task is complete.",
94
95
  "If the criteria are not met, do not call it: keep working and expect another continuation.",
95
- "Before your first loop_complete this session, read the pi-loop skill: it describes what the evidence gate accepts as a citation and what it refuses.",
96
+ `Before your first loop_complete this session, read ${LOOP_CRAFT_DOC}: it describes what the evidence gate accepts as a citation and what it refuses.`,
96
97
  ],
97
98
  parameters: Type.Object({
98
99
  loop_id: Type.String({
package/src/decide.ts CHANGED
@@ -37,9 +37,9 @@ function isWaiting(loop: LoopState, now: number): boolean {
37
37
  * An expiring loop gets one last turn to write its state into the ledger
38
38
  * before it stops; a loop already spending that turn stops immediately.
39
39
  */
40
- export type ExpiryReason = "loop-expired" | "expiry-final-wake";
40
+ type ExpiryReason = "loop-expired" | "expiry-final-wake";
41
41
 
42
- export type SkipReason =
42
+ type SkipReason =
43
43
  | "plan-mode-active"
44
44
  | "agent-busy"
45
45
  | "compaction-in-flight"
package/src/docs.ts ADDED
@@ -0,0 +1,21 @@
1
+ import { join } from "node:path";
2
+
3
+ /**
4
+ * The loop-craft document: the judgment the engine cannot encode — how an
5
+ * objective becomes falsifiable criteria, what the evidence gate accepts as a
6
+ * citation, when to `loop_wait`, and when work belongs in no loop at all.
7
+ *
8
+ * It used to ship as a skill. A skill buys one thing an injected pointer
9
+ * cannot: a description line in every system prompt, so the model could
10
+ * propose a loop unprompted. Across ~220 sessions after it shipped, every read
11
+ * of the file was triggered by the planning hint or `loop_complete`'s
12
+ * guidelines — never by the description — and the model never suggested
13
+ * `/loop` on its own. So the line was a tax on every session that never ran a
14
+ * loop (~95% of them) and bought nothing. An absolute path, injected only
15
+ * while a loop is being drafted or completed, is the same document at zero
16
+ * cost outside those moments, and a hard path beats "if it is available".
17
+ *
18
+ * The path is resolved from this module's own location so it survives every
19
+ * install layout (git, npm, workspace symlink, `npm link`).
20
+ */
21
+ export const LOOP_CRAFT_DOC = join(import.meta.dirname, "..", "docs", "loop-craft.md");
package/src/errors.ts CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  type Usage,
20
20
  } from "@earendil-works/pi-ai";
21
21
 
22
- export type LoopInterruption =
22
+ type LoopInterruption =
23
23
  /** The turn finished normally. */
24
24
  | "none"
25
25
  /** The user (or another extension) aborted the turn. */
@@ -27,14 +27,14 @@ import { LOOP_STATE_ENTRY_TYPE } from "./state.js";
27
27
  type NewSessionOptions = Exclude<Parameters<ExtensionCommandContext["newSession"]>[0], undefined>;
28
28
  type SessionManagerLike = Parameters<NonNullable<NewSessionOptions["setup"]>>[0];
29
29
 
30
- export type FreshLoopResult =
30
+ type FreshLoopResult =
31
31
  | { kind: "started" }
32
32
  | { kind: "cancelled" }
33
33
  /** The session exists and holds the loop, but it could not be kicked off. */
34
34
  | { kind: "partial"; detail: string }
35
35
  | { kind: "rejected"; detail: string };
36
36
 
37
- export interface FreshLoopRequest {
37
+ interface FreshLoopRequest {
38
38
  built: BuiltLoop;
39
39
  /** Write the ledger for `built` before the handoff; returns a failure detail. */
40
40
  prepareLedger(): string | undefined;
package/src/interval.ts CHANGED
@@ -27,7 +27,7 @@ export function parseDuration(token: string): number | undefined {
27
27
  return ms > MAX_INTERVAL_MS ? undefined : ms;
28
28
  }
29
29
 
30
- export interface ParsedInterval {
30
+ interface ParsedInterval {
31
31
  requestedMs: number;
32
32
  /** Clamped to MIN_INTERVAL_MS; the caller must echo the effective value. */
33
33
  effectiveMs: number;
package/src/ledger.ts CHANGED
@@ -26,13 +26,13 @@ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
26
26
  import { join } from "node:path";
27
27
  import { getAgentDir } from "@earendil-works/pi-coding-agent";
28
28
 
29
- export const LEDGER_DIR_NAME = "loop";
29
+ const LEDGER_DIR_NAME = "loop";
30
30
  export const CRITERIA_FILE = "criteria.json";
31
31
  export const PROGRESS_FILE = "PROGRESS.md";
32
32
 
33
33
  /** Cap on a loop's criteria: an objective is a paragraph, not a backlog. */
34
- export const MAX_CRITERIA = 12;
35
- export const MAX_DESCRIPTION_LENGTH = 500;
34
+ const MAX_CRITERIA = 12;
35
+ const MAX_DESCRIPTION_LENGTH = 500;
36
36
 
37
37
  export interface LoopCriterion {
38
38
  id: string;
@@ -75,7 +75,7 @@ export const MAX_EVIDENCE_LENGTH = 4000;
75
75
  /** The template's placeholders, replaced rather than appended to on first write. */
76
76
  const PLACEHOLDERS = new Set(["not started.", "- (nothing yet)"]);
77
77
 
78
- export function loopLedgerDir(loopId: string, agentDir = getAgentDir()): string {
78
+ function loopLedgerDir(loopId: string, agentDir = getAgentDir()): string {
79
79
  return join(agentDir, LEDGER_DIR_NAME, loopId);
80
80
  }
81
81
 
@@ -234,7 +234,7 @@ export function createLedger(
234
234
  }
235
235
  }
236
236
 
237
- export function progressTemplate(objective: string): string {
237
+ function progressTemplate(objective: string): string {
238
238
  return [
239
239
  "# Loop progress ledger",
240
240
  "",
@@ -320,7 +320,7 @@ function headingText(line: string | undefined): string | undefined {
320
320
  return match ? match[1].toLowerCase() : undefined;
321
321
  }
322
322
 
323
- export interface MarkCriterionResult {
323
+ interface MarkCriterionResult {
324
324
  ok: boolean;
325
325
  message: string;
326
326
  criteria?: LoopCriterion[];
@@ -18,7 +18,7 @@ import type { ActionsScreen } from "@narumitw/pi-tui-kit";
18
18
  import { formatDuration } from "./interval.js";
19
19
  import type { LoopProposal } from "./planning.js";
20
20
 
21
- export type LoopApprovalAction =
21
+ type LoopApprovalAction =
22
22
  | "start-here"
23
23
  | "start-fresh"
24
24
  | "change-cadence"
@@ -27,7 +27,7 @@ export type LoopApprovalAction =
27
27
 
28
28
  type Screen = "approval";
29
29
 
30
- export interface LoopApprovalMenuOptions {
30
+ interface LoopApprovalMenuOptions {
31
31
  proposal: LoopProposal;
32
32
  signal?: AbortSignal;
33
33
  isCurrent?(): boolean;
package/src/loop-env.ts CHANGED
@@ -25,8 +25,8 @@
25
25
 
26
26
  import type { LoopState } from "./state.js";
27
27
 
28
- export const LOOP_ACTIVE_ENV = "PI_LOOP_ACTIVE";
29
- export const LOOP_ID_ENV = "PI_LOOP_ID";
28
+ const LOOP_ACTIVE_ENV = "PI_LOOP_ACTIVE";
29
+ const LOOP_ID_ENV = "PI_LOOP_ID";
30
30
 
31
31
  /**
32
32
  * Publish (or withdraw) the loop-active signal for `loop`.
@@ -15,9 +15,9 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
15
15
  import type { ActionsScreen, DetailScreen } from "@narumitw/pi-tui-kit";
16
16
  import { defineMenu, runMenu } from "@narumitw/pi-tui-kit";
17
17
 
18
- export type LoopLaunchScreen = "main" | "how";
19
- export type LoopLaunchAction = "start-planning" | "settings";
20
- export type LoopPlanningAction = "request-proposal" | "cancel" | "settings";
18
+ type LoopLaunchScreen = "main" | "how";
19
+ type LoopLaunchAction = "start-planning" | "settings";
20
+ type LoopPlanningAction = "request-proposal" | "cancel" | "settings";
21
21
 
22
22
  /**
23
23
  * What a loop actually is, for someone who has never run one.
@@ -95,7 +95,7 @@ export function loopPlanningScreen(): ActionsScreen<LoopLaunchScreen, LoopPlanni
95
95
  };
96
96
  }
97
97
 
98
- export interface LoopLaunchMenuOptions {
98
+ interface LoopLaunchMenuOptions {
99
99
  signal?: AbortSignal;
100
100
  isCurrent?(): boolean;
101
101
  startPlanning(): void;
@@ -120,7 +120,7 @@ export async function showLoopLaunchMenu(ctx: ExtensionContext, options: LoopLau
120
120
  return runMenu(ctx, menu, { getState: () => undefined, ...lifecycle(options) });
121
121
  }
122
122
 
123
- export interface LoopPlanningMenuOptions {
123
+ interface LoopPlanningMenuOptions {
124
124
  signal?: AbortSignal;
125
125
  isCurrent?(): boolean;
126
126
  requestProposal(): void;
@@ -18,8 +18,8 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
18
18
  import type { ActionsScreen, DetailScreen, InputScreen } from "@narumitw/pi-tui-kit";
19
19
  import { defineMenu, runMenu } from "@narumitw/pi-tui-kit";
20
20
 
21
- export type LoopManagerScreen = "main" | "status" | "focus" | "cadence";
22
- export type LoopManagerAction =
21
+ type LoopManagerScreen = "main" | "status" | "focus" | "cadence";
22
+ type LoopManagerAction =
23
23
  | "pause"
24
24
  | "resume"
25
25
  | "stop"
@@ -128,7 +128,7 @@ export function loopCadenceScreen(view: LoopManagerView): InputScreen<LoopManage
128
128
  };
129
129
  }
130
130
 
131
- export interface LoopManagerMenuOptions {
131
+ interface LoopManagerMenuOptions {
132
132
  getView(): LoopManagerView | undefined;
133
133
  signal?: AbortSignal;
134
134
  isCurrent?(): boolean;
package/src/loop.ts CHANGED
@@ -99,7 +99,7 @@ import {
99
99
  updateLoopWidget,
100
100
  } from "./widget.js";
101
101
 
102
- export const LOOP_STATUS_KEY = "loop";
102
+ const LOOP_STATUS_KEY = "loop";
103
103
 
104
104
  /** Custom message type of the kickoff anchor. */
105
105
  export const LOOP_ANCHOR_MESSAGE_TYPE = "loop-objective";
@@ -109,7 +109,7 @@ export const LOOP_ANCHOR_MESSAGE_TYPE = "loop-objective";
109
109
  * capped here. Deterministic and small: the point is to stop re-waking a loop
110
110
  * that has nothing to do, not to invent an adaptive scheduler.
111
111
  */
112
- export const MAX_FALLBACK_BACKOFF = 4;
112
+ const MAX_FALLBACK_BACKOFF = 4;
113
113
 
114
114
  /**
115
115
  * How long a single run may stay open before the widget calls it blocked.
@@ -129,10 +129,10 @@ export const STALL_ATTENTION_MS = 900_000;
129
129
  * is not proof a turn will start. The ordinary dead-delivery counter cannot
130
130
  * catch this one, because after expiry there is no next delivery to count.
131
131
  */
132
- export const EXPIRY_TURN_GRACE_MS = 60_000;
132
+ const EXPIRY_TURN_GRACE_MS = 60_000;
133
133
 
134
134
  /** Consecutive loop deliveries that produce no run before the loop pauses. */
135
- export const MAX_DEAD_DELIVERIES = 3;
135
+ const MAX_DEAD_DELIVERIES = 3;
136
136
 
137
137
  /** Why the loop caused the run that is currently in flight. */
138
138
  type RunOrigin = "continuation" | "fallback";
@@ -145,7 +145,7 @@ type RunOrigin = "continuation" | "fallback";
145
145
  * actions share that path — one installs the loop here, the other hands it to
146
146
  * a fresh session — so the decision is returned and each caller renders it.
147
147
  */
148
- export type LoopStartResult = { ok: true; loop: LoopState } | { ok: false; message: string };
148
+ type LoopStartResult = { ok: true; loop: LoopState } | { ok: false; message: string };
149
149
 
150
150
  /**
151
151
  * A loop that exists but is not running anywhere: everything `installLoop`
@@ -160,7 +160,7 @@ export interface BuiltLoop {
160
160
  requestedMs: number;
161
161
  }
162
162
 
163
- export type LoopBuildResult = { ok: true; built: BuiltLoop } | { ok: false; message: string };
163
+ type LoopBuildResult = { ok: true; built: BuiltLoop } | { ok: false; message: string };
164
164
 
165
165
  interface ContinuationIntent {
166
166
  loopId: string;
package/src/manager.ts CHANGED
@@ -160,7 +160,7 @@ function setCadence(
160
160
  return true;
161
161
  }
162
162
 
163
- export async function showLoopSettings(
163
+ async function showLoopSettings(
164
164
  controller: LoopController,
165
165
  ctx: ExtensionCommandContext,
166
166
  ): Promise<void> {
package/src/planning.ts CHANGED
@@ -17,6 +17,7 @@
17
17
  * gone: both authored an acceptance gate in one line, unreviewed.
18
18
  */
19
19
 
20
+ import { LOOP_CRAFT_DOC } from "./docs.js";
20
21
  import { deriveCriteria, type LoopCriterion } from "./ledger.js";
21
22
  import { formatDuration } from "./interval.js";
22
23
 
@@ -132,7 +133,7 @@ export function renderProposalCard(proposal: LoopProposal): string[] {
132
133
  export const LOOP_PLANNING_HINT = [
133
134
  "<system-reminder>",
134
135
  "The user opened loop planning. You are drafting a loop with them; no loop is running and none starts until they approve one on the card.",
135
- "Read the pi-loop skill before drafting if it is available: it carries the objective, criteria, cadence and evidence craft in depth.",
136
+ `Before drafting, read ${LOOP_CRAFT_DOC}: it carries the objective, criteria, cadence and evidence craft in depth.`,
136
137
  "Cover three things in the conversation, then call loop_propose:",
137
138
  "- The objective, written as an acceptance test. One requirement per bullet; a conjunction inside a sentence does not split, so 'fix the flaky test and update the docs' becomes one criterion whose evidence must cover both halves. Name the check in the requirement itself ('…, verified by npm test passing'). The two questions that fix most objectives: how will we know it is done, and what command proves it?",
138
139
  "- The cadence: how long the loop may run before it expires, and the fallback heartbeat for a session that goes quiet. The loop advances whenever the session settles, so the heartbeat only matters when it is waiting on something.",
@@ -33,7 +33,7 @@ import {
33
33
  } from "./ledger.js";
34
34
  import type { LoopController } from "./loop.js";
35
35
 
36
- export const LOOP_PROGRESS_TOOL = "loop_progress";
36
+ const LOOP_PROGRESS_TOOL = "loop_progress";
37
37
 
38
38
  export function registerLoopProgressTool(pi: ExtensionAPI, controller: LoopController) {
39
39
  pi.registerTool(
@@ -18,7 +18,7 @@ import { formatDuration, MAX_INTERVAL_MS, parseDuration } from "./interval.js";
18
18
  import type { LoopController } from "./loop.js";
19
19
  import { MAX_GROUND_RULE_LENGTH, MAX_GROUND_RULES } from "./planning.js";
20
20
 
21
- export const LOOP_PROPOSE_TOOL = "loop_propose";
21
+ const LOOP_PROPOSE_TOOL = "loop_propose";
22
22
 
23
23
  export function registerLoopProposeTool(
24
24
  pi: ExtensionAPI,
package/src/safety.ts CHANGED
@@ -20,7 +20,7 @@
20
20
 
21
21
  import { createHash } from "node:crypto";
22
22
 
23
- export interface NoProgressState {
23
+ interface NoProgressState {
24
24
  toolFreeRepeatCount: number;
25
25
  lastFingerprint?: string;
26
26
  }
package/src/settings.ts CHANGED
@@ -11,9 +11,9 @@ import { basename, dirname, join } from "node:path";
11
11
  import { getAgentDir } from "@earendil-works/pi-coding-agent";
12
12
  import { parseDuration } from "./interval.js";
13
13
 
14
- export const LOOP_SETTINGS_FILE = "pi-loop.json";
14
+ const LOOP_SETTINGS_FILE = "pi-loop.json";
15
15
 
16
- export interface LoopCompactionSettings {
16
+ interface LoopCompactionSettings {
17
17
  enabled: boolean;
18
18
  /** Fraction of the context window that triggers a proactive compact. */
19
19
  threshold: number;
@@ -82,7 +82,7 @@ export const DEFAULT_LOOP_SETTINGS: LoopSettings = {
82
82
  },
83
83
  };
84
84
 
85
- export type LoopSettingsLoadResult =
85
+ type LoopSettingsLoadResult =
86
86
  | { kind: "missing"; settings: LoopSettings }
87
87
  | { kind: "invalid"; reason: string; settings: LoopSettings }
88
88
  | { kind: "loaded"; settings: LoopSettings };
package/src/state.ts CHANGED
@@ -11,8 +11,8 @@ export const PLAN_MODE_STATE_ENTRY_TYPE = "plan-mode-state";
11
11
 
12
12
  import { type LoopWait, normalizeLoopWait } from "./wait.js";
13
13
 
14
- export const LOOP_STATUSES = ["active", "paused", "stopped"] as const;
15
- export type LoopStatus = (typeof LOOP_STATUSES)[number];
14
+ const LOOP_STATUSES = ["active", "paused", "stopped"] as const;
15
+ type LoopStatus = (typeof LOOP_STATUSES)[number];
16
16
 
17
17
  export interface LoopState {
18
18
  id: string;
package/src/widget.ts CHANGED
@@ -45,13 +45,13 @@ interface WidgetTheme {
45
45
  type WidgetHost = { setWidget?: unknown };
46
46
 
47
47
  /** Criteria progress, absent when the loop has no readable ledger. */
48
- export interface CriteriaProgress {
48
+ interface CriteriaProgress {
49
49
  met: number;
50
50
  total: number;
51
51
  }
52
52
 
53
53
  /** The loop is being drafted with the user and has not started. */
54
- export interface LoopPlanningView {
54
+ interface LoopPlanningView {
55
55
  kind: "planning";
56
56
  /** Criteria in the proposed draft, once one has been put up for approval. */
57
57
  proposedCriteria?: number;