@henryqw/pi-prompt-creator 3.1.4 → 3.1.6

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
@@ -1,24 +1,23 @@
1
- # pi-prompt-creator — Context
1
+ # Pi Prompt Creator
2
2
 
3
- ## Domain
3
+ Pi Prompt Creator finds reusable prompt ideas in the current conversation. Main and the user review each idea before the user approves a new global prompt.
4
4
 
5
- Turn repetition and correction in the current Pi conversation into one editable prompt template candidate. The user decides whether the candidate becomes a global Pi prompt.
5
+ ## Language
6
6
 
7
- ## Glossary
7
+ **Current Conversation**:
8
+ The model-visible conversation on the active branch, including its retained summary and effective user and completed assistant text. Inactive branches, removed context, tool traffic, and thinking are not prompt-analysis evidence.
8
9
 
9
- - **Current Conversation**: The active conversation branch, including summaries that replace compacted history. Inactive branches are outside the scope.
10
- - **Signal**: Evidence in the Current Conversation that a reusable prompt may help. Signals are explicit recurrence language, repetition of the same underlying request, or repeated correction of the agent's behavior.
11
- - **Prompt Candidate**: One unsaved, editable prompt template proposed from the strongest Signal. A session has at most one pending Prompt Candidate; the user may explicitly request another after the pending candidate is shown or dismissed.
12
- - **Prompt Review**: Conversational refinement of a Prompt Candidate by the user and Main.
13
- - **Final Prompt Draft**: Complete prompt-template Markdown authored by Main after Prompt Review. A Prompt Candidate is not a Final Prompt Draft.
14
- - **Approved Prompt**: A Final Prompt Draft the user explicitly accepts for creation as a global Pi prompt. Approval never authorizes replacing an existing prompt.
10
+ **Signal**:
11
+ Evidence that a reusable prompt could help: explicit recurrence language, requests with the same intent, or repeated corrections of Main's behavior.
15
12
 
16
- ## Boundary
13
+ **Prompt Candidate**:
14
+ One unsaved prompt template proposed from a Signal. A pending candidate is untrusted review material, not an approved prompt.
17
15
 
18
- | Concern | Home |
19
- | --- | --- |
20
- | Cross-session evidence and historical search | `@henryqw/pi-session-recall` |
21
- | Scripts, Skills, and other automation | Their owning package or repository |
22
- | This extension | Current-conversation Signal detection, Prompt Candidate generation, and Prompt Review |
16
+ **Prompt Review**:
17
+ Conversational refinement of a displayed Prompt Candidate by the user and Main.
23
18
 
24
- 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.
19
+ **Final Prompt Draft**:
20
+ The complete prompt-template Markdown in Main's latest valid retained review reply after the candidate was displayed. A Prompt Candidate is not a Final Prompt Draft.
21
+
22
+ **Approved Prompt**:
23
+ A Final Prompt Draft that the user explicitly accepts for creation as a new global Pi prompt. Approval does not permit replacement of an existing prompt.
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.6",
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"