bigpowers 2.88.0 → 2.88.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bigpowers",
3
- "version": "2.88.0",
3
+ "version": "2.88.1",
4
4
  "description": "81 skills — 73 agent skills synthesizing 17 years of software engineering discipline into a prescriptive methodology for solo developers",
5
5
  "keywords": [
6
6
  "gemini-extension"
package/.pi/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bigpowers",
3
- "version": "2.88.0",
3
+ "version": "2.88.1",
4
4
  "description": "81 skills — 73 agent skills synthesizing 17 years of software engineering discipline into a prescriptive methodology for solo developers",
5
5
  "keywords": [
6
6
  "pi-package"
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## [2.88.1](https://github.com/danielvm-git/bigpowers/compare/v2.88.0...v2.88.1) (2026-09-05)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **extensions:** prevent omp-hooks from crashing pi at startup ([#119](https://github.com/danielvm-git/bigpowers/issues/119)) ([a487565](https://github.com/danielvm-git/bigpowers/commit/a487565160cbb20fae7ee8d85504898186fbe167))
7
+
1
8
  # [2.88.0](https://github.com/danielvm-git/bigpowers/compare/v2.87.8...v2.88.0) (2026-09-01)
2
9
 
3
10
 
@@ -2,7 +2,8 @@
2
2
  // extensions/omp-hooks.ts
3
3
  // bigpowers — Single OMP extension entry exposing every source skill as a
4
4
  // slash command, a bigpowers_skill LLM tool, and safety policy guards.
5
- import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
5
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
6
+ import { Type } from "typebox";
6
7
  import { execSync } from "node:child_process";
7
8
  import { readdirSync, readFileSync, statSync } from "node:fs";
8
9
  import { dirname, join } from "node:path";
@@ -236,9 +237,12 @@ async function injectSkillPrompt(
236
237
  // ---------------------------------------------------------------------------
237
238
 
238
239
  export default function bigpowers(pi: ExtensionAPI) {
239
- const z = pi.zod;
240
-
241
- pi.setLabel("bigpowers");
240
+ // NOTE: Do not call action methods (setLabel, sendUserMessage, setModel, …)
241
+ // in the factory body. pi installs throwing stubs for them during extension
242
+ // loading and only binds real implementations afterward — a load-time call
243
+ // aborts the whole pi startup (BUG-2026-09-05, #119). Only registration
244
+ // methods (registerCommand/registerTool/on/registerFlag) are valid here;
245
+ // perform actions inside event/command handlers via the runtime-bound `pi`.
242
246
 
243
247
  // Discover skills from skills/ in the installed plugin package root.
244
248
  const skills = discoverSkills(join(pluginRoot(), "skills"));
@@ -262,18 +266,24 @@ export default function bigpowers(pi: ExtensionAPI) {
262
266
  label: "Bigpowers Skill",
263
267
  description:
264
268
  "Interact with bigpowers source skills. Use 'list' to enumerate available skills, 'get' to retrieve a skill's full markdown, and 'run' to activate a skill with arguments.",
265
- parameters: z.object({
266
- action: z
267
- .enum(["list", "get", "run"])
268
- .describe("Operation: list all skills, get one skill, or run a skill with arguments"),
269
- skill: z
270
- .string()
271
- .optional()
272
- .describe("Skill name — required for 'get' and 'run' actions"),
273
- args: z
274
- .string()
275
- .optional()
276
- .describe("User arguments passed to the skill — only used with 'run'"),
269
+ parameters: Type.Object({
270
+ action: Type.Union(
271
+ [Type.Literal("list"), Type.Literal("get"), Type.Literal("run")],
272
+ {
273
+ description:
274
+ "Operation: list all skills, get one skill, or run a skill with arguments",
275
+ },
276
+ ),
277
+ skill: Type.Optional(
278
+ Type.String({
279
+ description: "Skill name — required for 'get' and 'run' actions",
280
+ }),
281
+ ),
282
+ args: Type.Optional(
283
+ Type.String({
284
+ description: "User arguments passed to the skill — only used with 'run'",
285
+ }),
286
+ ),
277
287
  }),
278
288
  async execute(_id, params, _signal, _onUpdate, _ctx) {
279
289
  const { action, skill: skillName, args } = params;
package/package.json CHANGED
@@ -1,13 +1,8 @@
1
1
  {
2
2
  "name": "bigpowers",
3
- "version": "2.88.0",
3
+ "version": "2.88.1",
4
4
  "description": "73 agent skills synthesizing 17 years of software engineering discipline into a prescriptive methodology for solo developers",
5
5
  "main": "index.js",
6
- "omp": {
7
- "extensions": [
8
- "extensions/omp-hooks.ts"
9
- ]
10
- },
11
6
  "scripts": {
12
7
  "setup": "node bin/setup.js",
13
8
  "compliance": "bash scripts/audit-compliance.sh specs/verifications/features && bash scripts/validate-doctrine.sh && python3 scripts/check-skill-links.py",
@@ -34,6 +29,9 @@
34
29
  "pi-package"
35
30
  ],
36
31
  "pi": {
32
+ "extensions": [
33
+ "extensions/omp-hooks.ts"
34
+ ],
37
35
  "skills": [
38
36
  "./.pi/skills"
39
37
  ],
@@ -69,6 +67,7 @@
69
67
  "@semantic-release/git": "^10.0.1",
70
68
  "@semantic-release/github": "^11.0.1",
71
69
  "@semantic-release/npm": "^12.0.1",
72
- "semantic-release": "^24.2.0"
70
+ "semantic-release": "^24.2.0",
71
+ "typebox": "^1.3.7"
73
72
  }
74
73
  }
@@ -2,52 +2,86 @@
2
2
  // Runtime smoke for extensions/omp-hooks.ts.
3
3
  // Imports the extension, registers a fake ExtensionAPI, calls registered hooks,
4
4
  // and asserts the slash-command handler awaits injection.
5
+ //
6
+ // Load-phase contract (BUG-2026-09-05, #119): pi installs throwing stubs for
7
+ // ACTION methods during extension loading and only binds real implementations
8
+ // after the factory returns. Registration methods are valid during load. This
9
+ // fake models that contract so a load-time action call (e.g. pi.setLabel())
10
+ // fails here exactly as it does in real pi, instead of silently passing.
11
+ // See pi 0.85.1 src/core/extensions/loader.ts (createExtensionRuntime).
5
12
 
6
13
  import bigpowers from "../extensions/omp-hooks.ts";
7
14
 
8
15
  type AnyObj = Record<string, unknown>;
9
16
  type Listener = (...args: unknown[]) => unknown;
10
17
 
11
- function makeZod(): AnyObj {
12
- const chainable: AnyObj = {};
13
- chainable.optional = () => chainable;
14
- chainable.describe = () => chainable;
15
- return chainable;
16
- }
18
+ // pi's verbatim message when an action method is called during load.
19
+ const LOAD_PHASE_ERROR =
20
+ "Extension runtime not initialized. Action methods cannot be called during extension loading.";
21
+
22
+ // True while the factory body runs. Action methods throw while this is set.
23
+ let loading = true;
17
24
 
18
25
  const commands = new Map<string, { description: string; handler: Listener }>();
19
26
  const tools: AnyObj[] = [];
20
27
  const events = new Map<string, Listener>();
21
28
  const sendCalls: AnyObj[] = [];
22
29
 
30
+ // Action method: rejects if invoked during the loading phase, like real pi.
31
+ function action<T extends Listener>(fn: T): T {
32
+ return ((...args: unknown[]) => {
33
+ if (loading) throw new Error(LOAD_PHASE_ERROR);
34
+ return fn(...args);
35
+ }) as T;
36
+ }
37
+
23
38
  const pi: AnyObj = {
24
- setLabel: () => {},
25
- zod: {
26
- object: (_shape: AnyObj) => makeZod(),
27
- string: () => makeZod(),
28
- enum: () => makeZod(),
29
- },
39
+ // ----- Registration methods: valid during load -----
30
40
  registerCommand: (name: string, spec: { description: string; handler: Listener }) => {
31
41
  commands.set(name, spec);
32
42
  },
33
43
  registerTool: (tool: AnyObj) => {
34
44
  tools.push(tool);
35
45
  },
46
+ registerShortcut: (_shortcut: string, _opts: AnyObj) => {},
47
+ registerFlag: (_name: string, _opts: AnyObj) => {},
36
48
  on: (event: string, handler: Listener) => {
37
49
  events.set(event, handler);
38
50
  },
39
- sendUserMessage: async (content: string, opts?: { deliverAs?: string }) => {
51
+ // registerProvider() calls made in the factory are queued by real pi, not
52
+ // thrown — model that as a permissive no-op.
53
+ registerProvider: (..._args: unknown[]) => {},
54
+
55
+ // ----- Action methods: throw if called during load -----
56
+ setLabel: action((_entryId: string, _label?: string) => {}),
57
+ setSessionName: action((_name: string) => {}),
58
+ getSessionName: action(() => undefined),
59
+ appendEntry: action((_customType: string, _data?: unknown) => {}),
60
+ setModel: action(() => Promise.resolve(true)),
61
+ getActiveTools: action(() => [] as string[]),
62
+ getAllTools: action(() => [] as AnyObj[]),
63
+ setActiveTools: action((_names: string[]) => {}),
64
+ getCommands: action(() => [] as AnyObj[]),
65
+ getThinkingLevel: action(() => "off"),
66
+ setThinkingLevel: action((_level: string) => {}),
67
+ sendUserMessage: action(async (content: string, opts?: { deliverAs?: string }) => {
40
68
  sendCalls.push({ kind: "sendUserMessage", content, opts });
41
69
  return undefined;
42
- },
43
- sendMessage: async (msg: AnyObj, opts?: { triggerTurn?: boolean; deliverAs?: string }) => {
70
+ }),
71
+ sendMessage: action(async (msg: AnyObj, opts?: { triggerTurn?: boolean; deliverAs?: string }) => {
44
72
  sendCalls.push({ kind: "sendMessage", msg, opts });
45
73
  return undefined;
46
- },
74
+ }),
47
75
  };
48
76
 
77
+ // Load the extension under the load-phase contract. A load-time action call
78
+ // throws here and fails the smoke test (RED), matching pi's startup abort.
49
79
  await bigpowers(pi as unknown as never);
50
80
 
81
+ // Factory returned successfully: the runtime is now "bound". Action methods
82
+ // are callable from here on (handlers, tool executions).
83
+ loading = false;
84
+
51
85
  console.log(JSON.stringify({
52
86
  commands: commands.size,
53
87
  tool: tools[0]?.name,
@@ -19,20 +19,22 @@ pass() {
19
19
  echo "ok : $*"
20
20
  }
21
21
 
22
- # ── 1. Package has exactly one omp.extensions entry ────────────────────────
22
+ # ── 1. Package has exactly one pi.extensions entry ─────────────────────────
23
+ # pi reads extension paths from the `pi` manifest key (readPiManifest). The
24
+ # legacy `omp.extensions` key was never consumed by pi — see BUG-2026-09-05.
23
25
  echo "--- [OMP] manifest: single extension entry ---"
24
- EXT_COUNT=$(node -e "const p = require('./package.json'); const exts = p.omp?.extensions; if (!exts) { console.log(0); } else { console.log(exts.length); }")
26
+ EXT_COUNT=$(node -e "const p = require('./package.json'); const exts = p.pi?.extensions; if (!exts) { console.log(0); } else { console.log(exts.length); }")
25
27
  if [[ "$EXT_COUNT" -eq 1 ]]; then
26
- pass "omp.extensions has exactly 1 entry"
28
+ pass "pi.extensions has exactly 1 entry"
27
29
  else
28
- fail "omp.extensions has $EXT_COUNT entries (expected 1)"
30
+ fail "pi.extensions has $EXT_COUNT entries (expected 1)"
29
31
  fi
30
32
 
31
33
  # ── 2. Extension file exists ───────────────────────────────────────────────
32
34
  echo "--- [OMP] extension file exists ---"
33
- EXT_PATH=$(node -e "const p = require('./package.json'); console.log(p.omp?.extensions?.[0] || '')")
35
+ EXT_PATH=$(node -e "const p = require('./package.json'); console.log(p.pi?.extensions?.[0] || '')")
34
36
  if [[ -z "$EXT_PATH" ]]; then
35
- fail "No omp.extensions entry found"
37
+ fail "No pi.extensions entry found"
36
38
  else
37
39
  if [[ -f "$EXT_PATH" ]]; then
38
40
  pass "extension file '$EXT_PATH' exists"
@@ -93,6 +95,27 @@ if [[ -n "$EXT_PATH" && -f "$EXT_PATH" ]]; then
93
95
  fi
94
96
  fi
95
97
 
98
+ # ── 6. Runtime load smoke (load-phase contract, BUG-2026-09-05) ────────────
99
+ # Loads the extension through a fake ExtensionAPI that models pi's load-phase
100
+ # guard: action methods (setLabel, sendUserMessage, …) throw during the factory
101
+ # body, exactly as real pi does. Catches load-time action calls that abort pi
102
+ # startup (#119). Requires a Node with TypeScript type-stripping (>=22.6).
103
+ echo "--- [OMP] runtime load smoke (scripts/omp-smoke.ts) ---"
104
+ SMOKE="$REPO_ROOT/scripts/omp-smoke.ts"
105
+ if [[ ! -f "$SMOKE" ]]; then
106
+ fail "omp-smoke.ts not found at $SMOKE"
107
+ elif ! command -v node >/dev/null 2>&1; then
108
+ echo "warn: node unavailable — skipping runtime load smoke"
109
+ else
110
+ SMOKE_ERR=$(mktemp)
111
+ if node "$SMOKE" >/dev/null 2>"$SMOKE_ERR"; then
112
+ pass "extension loads without calling action methods during load"
113
+ else
114
+ fail "runtime load smoke failed — $(head -3 "$SMOKE_ERR" 2>/dev/null | tr '\n' ' ')"
115
+ fi
116
+ rm -f "$SMOKE_ERR"
117
+ fi
118
+
96
119
  # ── Summary ────────────────────────────────────────────────────────────────
97
120
  echo "---"
98
121
  if [[ "$ERRORS" -eq 0 ]]; then