@jwilger/pi-development-system 0.1.0 → 0.1.1

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.
@@ -13,19 +13,25 @@ const nonNegotiables = readFileSync(
13
13
  export default function developmentSystem(pi: ExtensionAPI): void {
14
14
  const state = createSessionState(pi);
15
15
 
16
- const refreshStatus = (ctx: ExtensionContext) => {
17
- ctx.ui.setStatus(STATUS_KEY, renderStatusLine(state.get()));
16
+ let lastCtx: ExtensionContext | undefined;
17
+
18
+ const refreshStatus = () => {
19
+ lastCtx?.ui.setStatus(STATUS_KEY, renderStatusLine(state.get()));
18
20
  };
19
21
 
20
- pi.on("session_start", (_event, ctx) => {
21
- state.rebuildFrom(ctx.sessionManager.getBranch().flatMap(toCustomEntry));
22
- refreshStatus(ctx);
23
- });
22
+ const rebuild = (ctx: ExtensionContext) => {
23
+ lastCtx = ctx;
24
+ state.rebuildFrom(
25
+ ctx.sessionManager
26
+ .getBranch()
27
+ .flatMap((e) => (e.type === "custom" ? [{ customType: e.customType, data: e.data }] : [])),
28
+ );
29
+ refreshStatus();
30
+ };
24
31
 
25
- pi.on("session_tree", (_event, ctx) => {
26
- state.rebuildFrom(ctx.sessionManager.getBranch().flatMap(toCustomEntry));
27
- refreshStatus(ctx);
28
- });
32
+ state.onChange(refreshStatus);
33
+ pi.on("session_start", (_event, ctx) => rebuild(ctx));
34
+ pi.on("session_tree", (_event, ctx) => rebuild(ctx));
29
35
 
30
36
  pi.on("before_agent_start", (event) => {
31
37
  applyPromptSection(event, state.get(), nonNegotiables);
@@ -38,11 +44,3 @@ export default function developmentSystem(pi: ExtensionAPI): void {
38
44
  },
39
45
  });
40
46
  }
41
-
42
- function toCustomEntry(entry: unknown): Array<{ customType: string; data: unknown }> {
43
- if (typeof entry !== "object" || entry === null) return [];
44
- const e = entry as { type?: unknown; customType?: unknown; data?: unknown };
45
- return e.type === "custom" && typeof e.customType === "string"
46
- ? [{ customType: e.customType, data: e.data }]
47
- : [];
48
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jwilger/pi-development-system",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "A pi extension package representing a seasoned approach to software development using a full AI SDLC.",
5
5
  "keywords": [
6
6
  "pi-package"
@@ -34,7 +34,9 @@ export function parseDevsysState(input: unknown): DevsysState | ParseError {
34
34
  const { phase, sizing, activeSlice, openDepartures, jev, lastPushAt } = input;
35
35
  if (!PHASES.includes(phase as Phase)) return parseError(`unknown phase: ${String(phase)}`);
36
36
  if (!JEV.includes(jev as JevStatus)) return parseError(`unknown jev status: ${String(jev)}`);
37
- if (!Array.isArray(openDepartures)) return parseError("openDepartures must be an array");
37
+ if (!Array.isArray(openDepartures) || !openDepartures.every(isRecord)) {
38
+ return parseError("openDepartures must be an array of objects");
39
+ }
38
40
  if (sizing !== undefined && !SIZINGS.includes(sizing as Sizing)) {
39
41
  return parseError(`unknown sizing: ${String(sizing)}`);
40
42
  }
@@ -58,6 +60,8 @@ export type SessionState = {
58
60
  get(): DevsysState;
59
61
  update(fn: (s: DevsysState) => DevsysState): void;
60
62
  rebuildFrom(entries: ReadonlyArray<{ customType: string; data: unknown }>): void;
63
+ /** Registers a listener run after every update(); returns an unsubscribe function. */
64
+ onChange(listener: (state: DevsysState) => void): () => void;
61
65
  };
62
66
 
63
67
  /**
@@ -66,11 +70,19 @@ export type SessionState = {
66
70
  */
67
71
  export function createSessionState(api: ExtensionAPI): SessionState {
68
72
  let current: DevsysState = initialState();
73
+ const listeners = new Set<(state: DevsysState) => void>();
69
74
  return {
70
75
  get: () => current,
71
76
  update(fn) {
72
77
  current = fn(current);
73
78
  api.appendEntry(STATE_ENTRY_TYPE, current);
79
+ for (const listener of listeners) listener(current);
80
+ },
81
+ onChange(listener) {
82
+ listeners.add(listener);
83
+ return () => {
84
+ listeners.delete(listener);
85
+ };
74
86
  },
75
87
  rebuildFrom(entries) {
76
88
  let rebuilt: DevsysState = initialState();