@henryqw/pi-prompt-creator 3.1.4 → 3.1.5

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/CONTEXT.md CHANGED
@@ -19,6 +19,7 @@ Turn repetition and correction in the current Pi conversation into one editable
19
19
  | --- | --- |
20
20
  | Cross-session evidence and historical search | `@henryqw/pi-session-recall` |
21
21
  | Scripts, Skills, and other automation | Their owning package or repository |
22
+ | User config load rules and launch reporting | `@henryqw/pi-config-store` ([ADR 004](../../docs/adr/004-extension-config-safety.md)) |
22
23
  | This extension | Current-conversation Signal detection, Prompt Candidate generation, and Prompt Review |
23
24
 
24
25
  The extension stays silent when it finds no Prompt Candidate. Background analysis remains visible while it is running. Manually requested candidates display when ready; automatic candidates wait for explicit user display, so background work never steals editor focus.
package/README.md CHANGED
@@ -89,7 +89,7 @@ A successful save reloads Pi resources. If reload fails, the prompt remains save
89
89
 
90
90
  The extension registers `pi-prompt-creator/draft`. Its default task profile is `fast`.
91
91
 
92
- Use `/task-models` to select the model and thinking level. The extension owns no model configuration.
92
+ Use `/task-models` to select the model and thinking level. The extension owns no model configuration. At session start and before each analysis, it shows each problem in the shared task-model config one time in each session.
93
93
 
94
94
  ## Config
95
95
 
@@ -104,9 +104,11 @@ Edit either field. Then run `/reload` to apply the change. Omitted fields use th
104
104
 
105
105
  A missing file quietly uses both defaults. Startup never creates or rewrites the file.
106
106
 
107
- Malformed config or unknown keys disable automatic analysis. Pi warns once and leaves the file unchanged.
107
+ At session start, Pi shows a warning with the file path and each problem for an unknown key or an invalid value. Pi ignores the unknown key. A misspelled key near a missing field, such as `automatc`, counts as an invalid value of that field. An invalid `inputThreshold` uses the default `3`.
108
108
 
109
- Only `/promptor automatic on` or `/promptor automatic off` writes the config. A toggle keeps the configured threshold.
109
+ Automatic analysis stays off for the session when `automatic` is invalid or misspelled, or when the file has malformed JSON, invalid UTF-8, more than 64 KiB, a read error, or a non-object root. Startup leaves the file unchanged.
110
+
111
+ Only `/promptor automatic on` or `/promptor automatic off` writes the config. A toggle reads the file again and keeps its current `inputThreshold`, also when another session changed it. A toggle also replaces a file with problems: it first shows the problems, then keeps the usable values and removes unknown keys. If another session changes that file before the write, the toggle stops and writes nothing.
110
112
 
111
113
  ## State and storage
112
114
 
@@ -10,7 +10,7 @@ import {
10
10
  type ExtensionContext,
11
11
  type SessionEntry,
12
12
  } from "@earendil-works/pi-coding-agent";
13
- import { createConfigStore } from "@henryqw/pi-config-store";
13
+ import { createConfigStore, field, parsers, registerConfigReporting } from "@henryqw/pi-config-store";
14
14
  import {
15
15
  createEphemeralSubagentExecutor,
16
16
  resolveRoleLaunch,
@@ -19,6 +19,7 @@ import {
19
19
  } from "@henryqw/pi-subagent";
20
20
  import {
21
21
  registerModelTask,
22
+ registerTaskModelsConfigReporting,
22
23
  type ModelTask,
23
24
  } from "@henryqw/pi-task-models";
24
25
 
@@ -60,7 +61,6 @@ or
60
61
  The object must have exactly these keys. name must start with a lowercase ASCII letter, use only lowercase ASCII letters, digits, and single hyphens, and be at most ${MAX_NAME_CHARS} characters. markdown must be nonempty, at most ${MAX_MARKDOWN_BYTES} UTF-8 bytes, and contain no C0 or C1 controls except tab and LF.`,
61
62
  } satisfies Role;
62
63
 
63
- type Config = { automatic: boolean; inputThreshold: number };
64
64
  type PromptCandidate = { name: string; markdown: string };
65
65
  type ConversationItem = { role: "summary" | "user" | "assistant"; text: string };
66
66
  type ContextMessage = ReturnType<typeof buildSessionContext>["messages"][number];
@@ -80,21 +80,6 @@ function exactKeys(value: Record<string, unknown>, keys: readonly string[]): boo
80
80
  return actual.length === keys.length && keys.every((key) => Object.hasOwn(value, key));
81
81
  }
82
82
 
83
- function parseConfig(value: unknown): Config {
84
- if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Config must be an object.");
85
- const config = value as Record<string, unknown>;
86
- const validKeys = Object.keys(config).every((key) => key === "automatic" || key === "inputThreshold");
87
- const { automatic = true, inputThreshold = DEFAULT_INPUT_THRESHOLD } = config;
88
- if (
89
- !validKeys
90
- || typeof automatic !== "boolean"
91
- || typeof inputThreshold !== "number"
92
- || !Number.isSafeInteger(inputThreshold)
93
- || inputThreshold < 1
94
- ) throw new Error("Config may contain automatic:boolean and inputThreshold:positive integer.");
95
- return { automatic, inputThreshold };
96
- }
97
-
98
83
  function isPromptName(value: unknown): value is string {
99
84
  return typeof value === "string" && value.length <= MAX_NAME_CHARS && NAME.test(value);
100
85
  }
@@ -253,17 +238,20 @@ async function createPromptFile(path: string, markdown: string): Promise<void> {
253
238
  export default function promptCreatorExtension(pi: ExtensionAPI, options: PromptCreatorOptions = {}): void {
254
239
  registerModelTask(pi, DRAFT_TASK);
255
240
  const agentDir = options.agentDir ?? getAgentDir();
256
- const configStore = createConfigStore<Config>({
241
+ const configStore = createConfigStore({
257
242
  extensionId: EXTENSION_ID,
258
243
  agentDir,
259
- defaults: () => ({ automatic: true, inputThreshold: DEFAULT_INPUT_THRESHOLD }),
260
- parse: parseConfig,
244
+ fields: {
245
+ automatic: field(parsers.boolean(), true),
246
+ inputThreshold: field(parsers.integer({ min: 1 }), DEFAULT_INPUT_THRESHOLD),
247
+ },
261
248
  });
249
+ const configReporter = registerConfigReporting(pi, [configStore]);
250
+ const reportTaskModels = registerTaskModelsConfigReporting(pi, agentDir);
262
251
  let executor = options.executor;
263
252
  let automatic = false;
264
253
  let inputThreshold = DEFAULT_INPUT_THRESHOLD;
265
254
  let automaticConsumed = false;
266
- let configWarned = false;
267
255
  let inputCount = 0;
268
256
  let branchGeneration = 0;
269
257
  let candidate: PromptCandidate | undefined;
@@ -328,6 +316,7 @@ export default function promptCreatorExtension(pi: ExtensionAPI, options: Prompt
328
316
  void Promise.resolve().then(() => getExecutor().run({
329
317
  signal: run.controller.signal,
330
318
  prepare: async () => {
319
+ reportTaskModels(ctx);
331
320
  const launch = resolveRoleLaunch(pi, ctx, {
332
321
  role: DRAFT_ROLE,
333
322
  task: DRAFT_TASK,
@@ -397,18 +386,11 @@ export default function promptCreatorExtension(pi: ExtensionAPI, options: Prompt
397
386
  activeRun?.controller.abort(new Error("Prompt Creator session changed."));
398
387
  activeRun = undefined;
399
388
  resetBranch(ctx);
400
- try {
401
- const config = configStore.loadSync().value;
402
- automatic = config.automatic;
403
- inputThreshold = config.inputThreshold;
404
- } catch {
405
- automatic = false;
406
- inputThreshold = DEFAULT_INPUT_THRESHOLD;
407
- if (!configWarned) {
408
- configWarned = true;
409
- ctx.ui.notify("Prompt Creator config is invalid. Automatic analysis is disabled; the file was left unchanged.", "warning");
410
- }
411
- }
389
+ const loaded = configStore.loadSync();
390
+ configReporter.report(ctx, configStore, loaded);
391
+ // A file-level problem or an invalid or misspelled `automatic` keeps automatic analysis off; a missing file uses the default.
392
+ automatic = loaded.status === "ok" && loaded.value.automatic && !loaded.issues.some(({ key }) => key === "" || key === "automatic");
393
+ inputThreshold = loaded.status === "ok" ? loaded.value.inputThreshold : DEFAULT_INPUT_THRESHOLD;
412
394
  });
413
395
 
414
396
  pi.on("input", (event, ctx) => {
@@ -491,11 +473,20 @@ export default function promptCreatorExtension(pi: ExtensionAPI, options: Prompt
491
473
  if (action === "automatic" && values.length === 1 && (values[0] === "on" || values[0] === "off")) {
492
474
  const next = values[0] === "on";
493
475
  try {
494
- await configStore.save({ automatic: next, inputThreshold });
476
+ // Write the latest file, not the session's cached threshold, and report its problems first.
477
+ const loaded = configStore.loadSync();
478
+ configReporter.report(ctx, configStore, loaded);
479
+ // No field disables this config, so a load always has a value.
480
+ if (loaded.status !== "ok") return;
481
+ let written = { ...loaded.value, automatic: next };
482
+ if (!loaded.issues.length) written = await configStore.update((latest) => ({ ...latest, automatic: next }));
483
+ // A file with problems is replaced with its usable values, and only if no other session changed it meanwhile.
484
+ else await configStore.replaceIfUnchanged(loaded, written);
495
485
  automatic = next;
486
+ inputThreshold = written.inputThreshold;
496
487
  ctx.ui.notify(`Automatic analysis ${next ? "enabled" : "disabled"}.`, "info");
497
- } catch {
498
- ctx.ui.notify("Could not save Prompt Creator config.", "error");
488
+ } catch (error) {
489
+ ctx.ui.notify(`Could not save Prompt Creator config: ${error instanceof Error ? error.message : String(error)}`, "error");
499
490
  }
500
491
  return;
501
492
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@henryqw/pi-prompt-creator",
3
- "version": "3.1.4",
3
+ "version": "3.1.5",
4
4
  "description": "Turn current Pi conversation signals into user-approved prompt templates.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -45,9 +45,9 @@
45
45
  ]
46
46
  },
47
47
  "dependencies": {
48
- "@henryqw/pi-config-store": "^3.0.0",
48
+ "@henryqw/pi-config-store": "^4.0.0",
49
49
  "@henryqw/pi-subagent": "^22.0.0 || ^23.0.0 || ^24.0.0 || ^25.0.0 || ^26.0.0 || ^27.0.0",
50
- "@henryqw/pi-task-models": "^8.0.0"
50
+ "@henryqw/pi-task-models": "^9.0.0"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@earendil-works/pi-coding-agent": "1.1.0"