pi-must-have-extension 0.4.13 → 0.4.15

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,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.15] - 2026-06-01
4
+
5
+ ### Changed
6
+ - Deferred config-loader and debug-logger module loading to reduce extension startup work.
7
+ - Widened the Pi coding-agent peer dependency range to include `^0.77.0 || ^0.78.0` and aligned the development dependency to `^0.78.0`.
8
+
9
+ ## [0.4.14] - 2026-05-26
10
+
11
+ ### Changed
12
+ - Widened peer dependency ranges to `^0.74.0 || ^0.75.0`.
13
+ - Aligned dev dependencies to `^0.75.5`.
14
+
3
15
  ## [0.4.13] - 2026-05-22
4
16
 
5
17
  ### Changed
package/package.json CHANGED
@@ -1,72 +1,72 @@
1
- {
2
- "name": "pi-must-have-extension",
3
- "version": "0.4.13",
4
- "description": "RFC 2119 keyword normalizer extension for the Pi coding agent.",
5
- "type": "module",
6
- "main": "./index.ts",
7
- "exports": {
8
- ".": "./index.ts"
9
- },
10
- "files": [
11
- "index.ts",
12
- "src",
13
- "asset",
14
- "config/config.example.jsonc",
15
- "config/replacements.custom-sample.jsonc",
16
- "README.md",
17
- "CHANGELOG.md",
18
- "LICENSE",
19
- "tsconfig.json"
20
- ],
21
- "scripts": {
22
- "build": "tsc -p tsconfig.json --noEmit",
23
- "lint": "npm run build",
24
- "test": "node --experimental-strip-types --test test/**/*.test.ts",
25
- "check": "npm run lint && npm run test"
26
- },
27
- "keywords": [
28
- "pi-package",
29
- "pi-extension",
30
- "pi-coding-agent",
31
- "pi",
32
- "coding-agent",
33
- "rfc2119",
34
- "rfc8174",
35
- "bcp14",
36
- "prompt-normalization",
37
- "prompt-rewrite",
38
- "prompt-engineering",
39
- "keyword-normalization",
40
- "compliance",
41
- "jsonc"
42
- ],
43
- "author": "MasuRii",
44
- "license": "MIT",
45
- "repository": {
46
- "type": "git",
47
- "url": "git+https://github.com/MasuRii/pi-must-have-extension.git"
48
- },
49
- "homepage": "https://github.com/MasuRii/pi-must-have-extension#readme",
50
- "bugs": {
51
- "url": "https://github.com/MasuRii/pi-must-have-extension/issues"
52
- },
53
- "engines": {
54
- "node": ">=20"
55
- },
56
- "publishConfig": {
57
- "access": "public"
58
- },
59
- "pi": {
60
- "extensions": [
61
- "./index.ts"
62
- ]
63
- },
64
- "peerDependencies": {
65
- "@earendil-works/pi-coding-agent": "^0.75.4"
66
- },
67
- "devDependencies": {
68
- "@types/node": "^25.9.1",
69
- "typescript": "^6.0.3",
70
- "@earendil-works/pi-coding-agent": "^0.75.4"
71
- }
72
- }
1
+ {
2
+ "name": "pi-must-have-extension",
3
+ "version": "0.4.15",
4
+ "description": "RFC 2119 keyword normalizer extension for the Pi coding agent.",
5
+ "type": "module",
6
+ "main": "./index.ts",
7
+ "exports": {
8
+ ".": "./index.ts"
9
+ },
10
+ "files": [
11
+ "index.ts",
12
+ "src",
13
+ "asset",
14
+ "config/config.example.jsonc",
15
+ "config/replacements.custom-sample.jsonc",
16
+ "README.md",
17
+ "CHANGELOG.md",
18
+ "LICENSE",
19
+ "tsconfig.json"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsc -p tsconfig.json --noEmit",
23
+ "lint": "npm run build",
24
+ "test": "node --experimental-strip-types --test test/**/*.test.ts",
25
+ "check": "npm run lint && npm run test"
26
+ },
27
+ "keywords": [
28
+ "pi-package",
29
+ "pi-extension",
30
+ "pi-coding-agent",
31
+ "pi",
32
+ "coding-agent",
33
+ "rfc2119",
34
+ "rfc8174",
35
+ "bcp14",
36
+ "prompt-normalization",
37
+ "prompt-rewrite",
38
+ "prompt-engineering",
39
+ "keyword-normalization",
40
+ "compliance",
41
+ "jsonc"
42
+ ],
43
+ "author": "MasuRii",
44
+ "license": "MIT",
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/MasuRii/pi-must-have-extension.git"
48
+ },
49
+ "homepage": "https://github.com/MasuRii/pi-must-have-extension#readme",
50
+ "bugs": {
51
+ "url": "https://github.com/MasuRii/pi-must-have-extension/issues"
52
+ },
53
+ "engines": {
54
+ "node": ">=20"
55
+ },
56
+ "publishConfig": {
57
+ "access": "public"
58
+ },
59
+ "pi": {
60
+ "extensions": [
61
+ "./index.ts"
62
+ ]
63
+ },
64
+ "peerDependencies": {
65
+ "@earendil-works/pi-coding-agent": "^0.74.0 || ^0.75.0"
66
+ },
67
+ "devDependencies": {
68
+ "@types/node": "^25.9.1",
69
+ "typescript": "^6.0.3",
70
+ "@earendil-works/pi-coding-agent": "^0.75.5"
71
+ }
72
+ }
package/src/constants.ts CHANGED
@@ -1,8 +1,33 @@
1
- import { getAgentDir } from "@earendil-works/pi-coding-agent";
2
1
  import { homedir } from "node:os";
3
2
  import { join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
4
  import type { MustHaveExtensionConfig } from "./types.js";
5
5
 
6
+ function normalizeAgentDirPath(path: string): string {
7
+ if (path === "~") {
8
+ return homedir();
9
+ }
10
+
11
+ if (path.startsWith("~/") || (process.platform === "win32" && path.startsWith("~\\"))) {
12
+ return join(homedir(), path.slice(2));
13
+ }
14
+
15
+ if (/^file:\/\//.test(path)) {
16
+ return fileURLToPath(path);
17
+ }
18
+
19
+ return path;
20
+ }
21
+
22
+ function getAgentDir(): string {
23
+ const envDir = process.env.PI_CODING_AGENT_DIR;
24
+ if (envDir) {
25
+ return normalizeAgentDirPath(envDir);
26
+ }
27
+
28
+ return join(homedir(), ".pi", "agent");
29
+ }
30
+
6
31
  export const EXTENSION_NAME = "pi-must-have-extension";
7
32
  export const AGENT_DIR = getAgentDir();
8
33
  export const CONFIG_DIR = join(AGENT_DIR, "extensions", EXTENSION_NAME);
package/src/index.ts CHANGED
@@ -6,15 +6,62 @@ import {
6
6
  LEGACY_OPENCODE_CONFIG_PATH,
7
7
  LEGACY_PI_MUST_HAVE_PLUGIN_CONFIG_PATH,
8
8
  } from "./constants.js";
9
- import { ensureConfigExists, loadConfig } from "./config/config-loader.js";
10
- import { writeDebugLog } from "./debug-logger.js";
11
9
  import { applyReplacements, shouldSkipInput } from "./replacements/replacement-engine.js";
12
10
 
11
+ type ConfigLoaderModule = typeof import("./config/config-loader.js");
12
+ type DebugLoggerModule = typeof import("./debug-logger.js");
13
+
13
14
  interface ReplacementDebugDetail {
14
15
  value: string;
15
16
  count: number;
16
17
  }
17
18
 
19
+ let configLoaderModule: ConfigLoaderModule | undefined;
20
+ let configLoaderModulePromise: Promise<ConfigLoaderModule> | undefined;
21
+ let debugLoggerModule: DebugLoggerModule | undefined;
22
+ let debugLoggerModulePromise: Promise<DebugLoggerModule> | undefined;
23
+
24
+ function loadConfigLoaderModule(): Promise<ConfigLoaderModule> {
25
+ if (configLoaderModule) {
26
+ return Promise.resolve(configLoaderModule);
27
+ }
28
+
29
+ configLoaderModulePromise ??= import("./config/config-loader.js").then((module) => {
30
+ configLoaderModule = module;
31
+ return module;
32
+ });
33
+ return configLoaderModulePromise;
34
+ }
35
+
36
+ function loadDebugLoggerModule(): Promise<DebugLoggerModule> {
37
+ if (debugLoggerModule) {
38
+ return Promise.resolve(debugLoggerModule);
39
+ }
40
+
41
+ debugLoggerModulePromise ??= import("./debug-logger.js").then((module) => {
42
+ debugLoggerModule = module;
43
+ return module;
44
+ });
45
+ return debugLoggerModulePromise;
46
+ }
47
+
48
+ async function writeDebugLogWhenEnabled(
49
+ enabled: boolean,
50
+ event: string,
51
+ payload: Record<string, unknown> = {},
52
+ ): Promise<void> {
53
+ if (!enabled) {
54
+ return;
55
+ }
56
+
57
+ try {
58
+ const { writeDebugLog } = await loadDebugLoggerModule();
59
+ writeDebugLog(true, event, payload);
60
+ } catch {
61
+ // Debug logging must never affect extension behavior.
62
+ }
63
+ }
64
+
18
65
  function buildReplacementDebugDetails(
19
66
  counts: Map<string, number>,
20
67
  replacements: Record<string, string>,
@@ -34,30 +81,31 @@ function buildReplacementDebugDetails(
34
81
  export default function mustHaveExtension(pi: ExtensionAPI): void {
35
82
  const warnedMessages = new Set<string>();
36
83
 
37
- const warnOnce = (
84
+ const warnOnce = async (
38
85
  message: string,
39
86
  ctx: Pick<ExtensionContext, "hasUI" | "ui">,
40
87
  debugEnabled: boolean,
41
- ): void => {
88
+ ): Promise<void> => {
42
89
  if (warnedMessages.has(message)) {
43
90
  return;
44
91
  }
45
92
  warnedMessages.add(message);
46
- writeDebugLog(debugEnabled, "warning", { message });
93
+ await writeDebugLogWhenEnabled(debugEnabled, "warning", { message });
47
94
  if (ctx.hasUI) {
48
95
  ctx.ui.notify(message, "warning");
49
96
  }
50
97
  };
51
98
 
52
99
  pi.on("session_start", async (_event, ctx) => {
100
+ const { ensureConfigExists, loadConfig } = await loadConfigLoaderModule();
53
101
  const ensureResult = ensureConfigExists();
54
102
  const loaded = loadConfig();
55
103
  const debugEnabled = loaded.config.debug;
56
104
  if (ensureResult.error) {
57
- warnOnce(ensureResult.error, ctx, debugEnabled);
105
+ await warnOnce(ensureResult.error, ctx, debugEnabled);
58
106
  }
59
107
  if (ensureResult.migratedFrom) {
60
- warnOnce(
108
+ await warnOnce(
61
109
  `${EXTENSION_NAME}: migrated legacy config from ${ensureResult.migratedFrom} to ${CONFIG_PATH}.`,
62
110
  ctx,
63
111
  debugEnabled,
@@ -65,11 +113,11 @@ export default function mustHaveExtension(pi: ExtensionAPI): void {
65
113
  }
66
114
 
67
115
  if (loaded.warning) {
68
- warnOnce(loaded.warning, ctx, debugEnabled);
116
+ await warnOnce(loaded.warning, ctx, debugEnabled);
69
117
  }
70
118
 
71
119
  if (loaded.source === "legacy_pi_plugin") {
72
- warnOnce(
120
+ await warnOnce(
73
121
  `${EXTENSION_NAME}: using legacy config ${LEGACY_PI_MUST_HAVE_PLUGIN_CONFIG_PATH}. Move it to ${CONFIG_PATH}.`,
74
122
  ctx,
75
123
  debugEnabled,
@@ -77,7 +125,7 @@ export default function mustHaveExtension(pi: ExtensionAPI): void {
77
125
  }
78
126
 
79
127
  if (loaded.source === "legacy_plugin") {
80
- warnOnce(
128
+ await warnOnce(
81
129
  `${EXTENSION_NAME}: using legacy config ${LEGACY_MUST_HAVE_PLUGIN_CONFIG_PATH}. Move it to ${CONFIG_PATH}.`,
82
130
  ctx,
83
131
  debugEnabled,
@@ -85,7 +133,7 @@ export default function mustHaveExtension(pi: ExtensionAPI): void {
85
133
  }
86
134
 
87
135
  if (loaded.source === "legacy_opencode") {
88
- warnOnce(
136
+ await warnOnce(
89
137
  `${EXTENSION_NAME}: using legacy config ${LEGACY_OPENCODE_CONFIG_PATH}. Move it to ${CONFIG_PATH}.`,
90
138
  ctx,
91
139
  debugEnabled,
@@ -94,7 +142,7 @@ export default function mustHaveExtension(pi: ExtensionAPI): void {
94
142
 
95
143
  if (debugEnabled) {
96
144
  const replacementCount = Object.keys(loaded.config.replacements).length;
97
- writeDebugLog(true, "debug.enabled", {
145
+ await writeDebugLogWhenEnabled(true, "debug.enabled", {
98
146
  source: loaded.source,
99
147
  replacementCount,
100
148
  configPath: CONFIG_PATH,
@@ -111,9 +159,10 @@ export default function mustHaveExtension(pi: ExtensionAPI): void {
111
159
  return { action: "continue" as const };
112
160
  }
113
161
 
162
+ const { loadConfig } = await loadConfigLoaderModule();
114
163
  const loaded = loadConfig();
115
164
  if (loaded.warning) {
116
- warnOnce(loaded.warning, ctx, loaded.config.debug);
165
+ await warnOnce(loaded.warning, ctx, loaded.config.debug);
117
166
  }
118
167
 
119
168
  const replacements = loaded.config.replacements;
@@ -130,7 +179,7 @@ export default function mustHaveExtension(pi: ExtensionAPI): void {
130
179
  const totalReplacements = Array.from(counts.values()).reduce((sum, count) => sum + count, 0);
131
180
  const details = buildReplacementDebugDetails(counts, replacements);
132
181
  const summary = `${EXTENSION_NAME}: applied ${totalReplacements} replacement(s).`;
133
- writeDebugLog(true, "replacements.applied", {
182
+ await writeDebugLogWhenEnabled(true, "replacements.applied", {
134
183
  summary,
135
184
  replacements: details,
136
185
  });