pi-dev-flow 0.52.1 → 0.52.2

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
@@ -4,6 +4,12 @@ Format: newest first. `Unreleased` collects what's merged but not yet tagged.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.52.2] - 2026-10-21
8
+
9
+ ### Fixed
10
+
11
+ - **The `[dev-flow]` stage line printed twice per prompt.** One package reaches one runtime twice as fast as you install it — a project `git:` pin beside the global `npm:` package, or a dogfood checkout whose `.pi/extensions` pointer sits next to the installed copy — and pi dedupes extensions by **file path**, not by package, so both copies register and both answers were appended: two `[dev-flow]` lines in every prompt, in the logs and to the model. The stage-context handler now claims the run: pi builds **one ctx object per `before_agent_start`** and hands it to every registered handler, so the first pass stamps it and a rival copy answers nothing. The claim is per run rather than per process on purpose — pi-web hosts many sessions inside one Node process (dev-flow's own session map is keyed by session id for that reason), and a process-wide "only the first copy registers" rule would leave every later session without guards, without the `flow` tool and without stage context. Each copy still registers its guards, tool and command: their verdicts are identical, and pi exposes no runtime identity during load to dedupe them against.
12
+
7
13
  ## [0.52.1] - 2026-10-21
8
14
 
9
15
  ### Changed
package/README.md CHANGED
@@ -50,7 +50,9 @@ pi install ./pi-dev-flow # or a local checkout
50
50
  ```
51
51
 
52
52
  Later releases arrive with `pi update --extensions` (an npm install reconciles to the newest version);
53
- then `/reload` — a running session keeps the code it loaded at startup.
53
+ then `/reload` — a running session keeps the code it loaded at startup. Two sources at once (a project
54
+ `git:` pin beside the global `npm:` package) load two copies — pi dedupes extensions by path, not by
55
+ package — so keep the one you actually update from.
54
56
 
55
57
  Then, once per project repo, `/flow init`. It reads the repo (default branch and forge from git,
56
58
  package manager, test/build/run conventions, version manifest) and writes **`.dev-flow/project.md`
@@ -194,6 +194,9 @@ const compacting = new Set<string>();
194
194
  /** Seam for the takeover's summarize call (pi's own compact()); tests replace `.run` — mutable object stands in for ESM mocking. */
195
195
  export const compactionTakeover: { run: typeof compact } = { run: compact };
196
196
 
197
+ /** Marks the before_agent_start pass that already wrote this run's [dev-flow] line — see the claim in that handler. */
198
+ const CONTEXT_EMITTED = Symbol.for("pi-dev-flow.context-emitted");
199
+
197
200
  function unfinishedStates(root: string): FlowState[] {
198
201
  if (!existsSync(dfDir(root))) return [];
199
202
  return readdirSync(dfDir(root), { withFileTypes: true })
@@ -1464,6 +1467,14 @@ export default function devFlow(pi: ExtensionAPI) {
1464
1467
  });
1465
1468
 
1466
1469
  pi.on("before_agent_start", async (_event, ctx) => {
1470
+ // One [dev-flow] line per run, whatever number of copies of this package are installed.
1471
+ // The same package reaches one runtime twice all the time (a project pin beside the npm install,
1472
+ // or this checkout's .pi/extensions pointer beside it) and pi dedupes extensions by file path
1473
+ // only, so both copies answer and the line lands twice in the prompt. pi hands every handler of
1474
+ // a run the same ctx object, so the first pass claims the run and the rival stays silent.
1475
+ const run = ctx as unknown as Record<symbol, boolean | undefined>;
1476
+ if (run[CONTEXT_EMITTED]) return undefined;
1477
+ run[CONTEXT_EMITTED] = true;
1467
1478
  if (!existsSync(dfDir(ctx.cwd))) return undefined;
1468
1479
  const cfg = loadProject(dfDir(ctx.cwd)).cfg;
1469
1480
  const state = boundFeature(ctx.cwd, sessionKey(ctx));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-dev-flow",
3
- "version": "0.52.1",
3
+ "version": "0.52.2",
4
4
  "description": "Enforced feature workflow for Pi: plan → implement → pre-release → release, gated checkpoints, self-improvement reflections, one git worktree per feature, changelog + publish as part of the release",
5
5
  "type": "module",
6
6
  "keywords": [