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 +12 -0
- package/package.json +72 -72
- package/src/constants.ts +26 -1
- package/src/index.ts +63 -14
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.
|
|
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.
|
|
66
|
-
},
|
|
67
|
-
"devDependencies": {
|
|
68
|
-
"@types/node": "^25.9.1",
|
|
69
|
-
"typescript": "^6.0.3",
|
|
70
|
-
"@earendil-works/pi-coding-agent": "^0.75.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
182
|
+
await writeDebugLogWhenEnabled(true, "replacements.applied", {
|
|
134
183
|
summary,
|
|
135
184
|
replacements: details,
|
|
136
185
|
});
|