@mrclrchtr/supi-ask-user 1.7.0 → 1.8.0

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/README.md CHANGED
@@ -114,12 +114,10 @@ A completed form returns a result with `details.status` set to one of:
114
114
 
115
115
  The tool registers the following prompt guidance that the model sees:
116
116
 
117
- - Use ask_user only when explicit user input is required to proceed safely; do not use ask_user for open-ended interviews or repo facts.
117
+ - Use ask_user only for blocking user input, not open-ended interviews or repo facts.
118
118
  - Use ask_user with 1-4 related questions; prefer one when possible.
119
- - Use ask_user `choice` for fixed options and ask_user `text` for freeform input; model yes/no as `choice` with `{ value: "yes", label: "Yes" }` and `{ value: "no", label: "No" }`.
120
- - Use ask_user `allowOther` only on single-select `choice` questions.
121
- - Use ask_user `allowDiscuss` or `allowPartialSubmit` only when that outcome is actionable.
122
- - Do not call ask_user while another ask_user form is already in flight.
119
+ - Use ask_user `choice` for fixed options and ask_user `text` for freeform input; yes/no should be a `choice`.
120
+ - Keep one ask_user form active at a time; use `allowOther` only for single-select choice and `allowDiscuss`/`allowPartialSubmit` only when actionable.
123
121
 
124
122
  ## UI controls
125
123
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-core",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "SuPi core — shared infrastructure for SuPi extensions (XML context tags, config system)",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -37,8 +37,19 @@
37
37
  "main": "src/api.ts",
38
38
  "exports": {
39
39
  "./api": "./src/api.ts",
40
+ "./config": "./src/config.ts",
41
+ "./context": "./src/context.ts",
42
+ "./debug": "./src/debug-registry.ts",
40
43
  "./extension": "./src/extension.ts",
41
- "./package.json": "./package.json"
44
+ "./package.json": "./package.json",
45
+ "./path": "./src/path.ts",
46
+ "./project": "./src/project.ts",
47
+ "./session": "./src/session.ts",
48
+ "./settings": "./src/settings.ts",
49
+ "./settings-ui": "./src/settings-ui.ts",
50
+ "./terminal": "./src/terminal.ts",
51
+ "./tool-framework": "./src/tool-framework.ts",
52
+ "./types": "./src/types.ts"
42
53
  },
43
54
  "pi": {
44
55
  "extensions": [
@@ -1,96 +1,30 @@
1
1
  // supi-core — shared infrastructure for SuPi extensions.
2
2
  // Provides XML context tag wrapping, unified config system, context-message utilities,
3
3
  // settings registry for supi-wide TUI settings, and a shared tool-spec/registration framework.
4
+ //
5
+ // Convenience barrel — re-exports all domain entry points.
6
+ // For lighter imports, use one of the domain subpaths directly
7
+ // (e.g. @mrclrchtr/supi-core/config, @mrclrchtr/supi-core/context).
4
8
 
5
- export type { SupiConfigLocation, SupiConfigOptions } from "./config/config.ts";
6
- export {
7
- loadSupiConfig,
8
- loadSupiConfigForScope,
9
- readJsonFile,
10
- removeSupiConfigKey,
11
- writeSupiConfig,
12
- } from "./config/config.ts";
13
- export type { ConfigSettingsHelpers, ConfigSettingsOptions } from "./config/config-settings.ts";
14
- export { registerConfigSettings } from "./config/config-settings.ts";
15
- export type { ContextMessageLike } from "./context/context-messages.ts";
16
- export {
17
- findLastUserMessageIndex,
18
- getContextToken,
19
- getPromptContent,
20
- pruneAndReorderContextMessages,
21
- restorePromptContent,
22
- } from "./context/context-messages.ts";
23
- export type { ContextProvider } from "./context/context-provider-registry.ts";
24
- export {
25
- clearRegisteredContextProviders,
26
- getRegisteredContextProviders,
27
- registerContextProvider,
28
- } from "./context/context-provider-registry.ts";
29
- export { wrapExtensionContext } from "./context/context-tag.ts";
30
- export type {
31
- DebugAgentAccess,
32
- DebugEvent,
33
- DebugEventInput,
34
- DebugEventQuery,
35
- DebugEventQueryResult,
36
- DebugEventView,
37
- DebugLevel,
38
- DebugNotifyLevel,
39
- DebugRegistryConfig,
40
- DebugSummary,
41
- } from "./debug-registry.ts";
42
- export {
43
- clearDebugEvents,
44
- configureDebugRegistry,
45
- DEBUG_REGISTRY_DEFAULTS,
46
- getDebugEvents,
47
- getDebugRegistryConfig,
48
- getDebugSummary,
49
- recordDebugEvent,
50
- redactDebugData,
51
- resetDebugRegistry,
52
- } from "./debug-registry.ts";
53
- export { fileToUri, resolveToolPath, stripToolPathPrefix, uriToFile } from "./path-utils.ts";
54
- export type { KnownRootEntry } from "./project-roots.ts";
55
- export {
56
- buildKnownRootsMap,
57
- byPathDepth,
58
- dedupeTopmostRoots,
59
- findProjectRoot,
60
- isWithin,
61
- isWithinOrEqual,
62
- mergeKnownRoots,
63
- resolveKnownRoot,
64
- segmentCount,
65
- sortRootsBySpecificity,
66
- walkProject,
67
- } from "./project-roots.ts";
68
- export { createRegistry, createSessionStateRegistry } from "./registry-utils.ts";
69
- export { getActiveBranchEntries } from "./session-utils.ts";
70
- export { registerSettingsCommand } from "./settings/settings-command.ts";
71
- export type { SettingsScope, SettingsSection } from "./settings/settings-registry.ts";
72
- export {
73
- clearRegisteredSettings,
74
- getRegisteredSettings,
75
- registerSettings,
76
- } from "./settings/settings-registry.ts";
77
- export { createInputSubmenu, openSettingsOverlay } from "./settings/settings-ui.ts";
78
- export type { TitleTarget } from "./terminal.ts";
79
- export {
80
- DONE_SYMBOL,
81
- formatTitle,
82
- signalBell,
83
- signalDone,
84
- signalWaiting,
85
- WAITING_SYMBOL,
86
- } from "./terminal.ts";
87
- export type { SuiPiToolPromptSurface, SuiPiToolSpec, ToolExecuteFn } from "./tool-framework.ts";
88
- export {
89
- CharacterParam,
90
- derivePromptSurface,
91
- FileParam,
92
- LineParam,
93
- MaxResultsParam,
94
- registerSuiPiTools,
95
- SymbolParam,
96
- } from "./tool-framework.ts";
9
+ // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
10
+ export * from "./config.ts";
11
+ // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
12
+ export * from "./context.ts";
13
+ // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
14
+ export * from "./debug-registry.ts";
15
+ // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
16
+ export * from "./path.ts";
17
+ // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
18
+ export * from "./project.ts";
19
+ // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
20
+ export * from "./session.ts";
21
+ // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
22
+ export * from "./settings.ts";
23
+ // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
24
+ export * from "./settings-ui.ts";
25
+ // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
26
+ export * from "./terminal.ts";
27
+ // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
28
+ export * from "./tool-framework.ts";
29
+ // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
30
+ export * from "./types.ts";
@@ -0,0 +1,11 @@
1
+ // supi-core config domain — config loading and config-settings helpers.
2
+ export type { SupiConfigLocation, SupiConfigOptions } from "./config/config.ts";
3
+ export {
4
+ loadSupiConfig,
5
+ loadSupiConfigForScope,
6
+ readJsonFile,
7
+ removeSupiConfigKey,
8
+ writeSupiConfig,
9
+ } from "./config/config.ts";
10
+ export type { ConfigSettingsHelpers, ConfigSettingsOptions } from "./config/config-settings.ts";
11
+ export { registerConfigSettings } from "./config/config-settings.ts";
@@ -0,0 +1,16 @@
1
+ // supi-core context domain — context messages, providers, and XML tags.
2
+ export type { ContextMessageLike } from "./context/context-messages.ts";
3
+ export {
4
+ findLastUserMessageIndex,
5
+ getContextToken,
6
+ getPromptContent,
7
+ pruneAndReorderContextMessages,
8
+ restorePromptContent,
9
+ } from "./context/context-messages.ts";
10
+ export type { ContextProvider } from "./context/context-provider-registry.ts";
11
+ export {
12
+ clearRegisteredContextProviders,
13
+ getRegisteredContextProviders,
14
+ registerContextProvider,
15
+ } from "./context/context-provider-registry.ts";
16
+ export { wrapExtensionContext } from "./context/context-tag.ts";
@@ -1,96 +1,30 @@
1
1
  // supi-core — shared infrastructure for SuPi extensions.
2
2
  // Provides XML context tag wrapping, unified config system, context-message utilities,
3
3
  // settings registry for supi-wide TUI settings, and a shared tool-spec/registration framework.
4
+ //
5
+ // Convenience barrel — re-exports all domain entry points.
6
+ // For lighter imports, use one of the domain subpaths directly
7
+ // (e.g. @mrclrchtr/supi-core/config, @mrclrchtr/supi-core/context).
4
8
 
5
- export type { SupiConfigLocation, SupiConfigOptions } from "./config/config.ts";
6
- export {
7
- loadSupiConfig,
8
- loadSupiConfigForScope,
9
- readJsonFile,
10
- removeSupiConfigKey,
11
- writeSupiConfig,
12
- } from "./config/config.ts";
13
- export type { ConfigSettingsHelpers, ConfigSettingsOptions } from "./config/config-settings.ts";
14
- export { registerConfigSettings } from "./config/config-settings.ts";
15
- export type { ContextMessageLike } from "./context/context-messages.ts";
16
- export {
17
- findLastUserMessageIndex,
18
- getContextToken,
19
- getPromptContent,
20
- pruneAndReorderContextMessages,
21
- restorePromptContent,
22
- } from "./context/context-messages.ts";
23
- export type { ContextProvider } from "./context/context-provider-registry.ts";
24
- export {
25
- clearRegisteredContextProviders,
26
- getRegisteredContextProviders,
27
- registerContextProvider,
28
- } from "./context/context-provider-registry.ts";
29
- export { wrapExtensionContext } from "./context/context-tag.ts";
30
- export type {
31
- DebugAgentAccess,
32
- DebugEvent,
33
- DebugEventInput,
34
- DebugEventQuery,
35
- DebugEventQueryResult,
36
- DebugEventView,
37
- DebugLevel,
38
- DebugNotifyLevel,
39
- DebugRegistryConfig,
40
- DebugSummary,
41
- } from "./debug-registry.ts";
42
- export {
43
- clearDebugEvents,
44
- configureDebugRegistry,
45
- DEBUG_REGISTRY_DEFAULTS,
46
- getDebugEvents,
47
- getDebugRegistryConfig,
48
- getDebugSummary,
49
- recordDebugEvent,
50
- redactDebugData,
51
- resetDebugRegistry,
52
- } from "./debug-registry.ts";
53
- export { fileToUri, resolveToolPath, stripToolPathPrefix, uriToFile } from "./path-utils.ts";
54
- export type { KnownRootEntry } from "./project-roots.ts";
55
- export {
56
- buildKnownRootsMap,
57
- byPathDepth,
58
- dedupeTopmostRoots,
59
- findProjectRoot,
60
- isWithin,
61
- isWithinOrEqual,
62
- mergeKnownRoots,
63
- resolveKnownRoot,
64
- segmentCount,
65
- sortRootsBySpecificity,
66
- walkProject,
67
- } from "./project-roots.ts";
68
- export { createRegistry, createSessionStateRegistry } from "./registry-utils.ts";
69
- export { getActiveBranchEntries } from "./session-utils.ts";
70
- export { registerSettingsCommand } from "./settings/settings-command.ts";
71
- export type { SettingsScope, SettingsSection } from "./settings/settings-registry.ts";
72
- export {
73
- clearRegisteredSettings,
74
- getRegisteredSettings,
75
- registerSettings,
76
- } from "./settings/settings-registry.ts";
77
- export { createInputSubmenu, openSettingsOverlay } from "./settings/settings-ui.ts";
78
- export type { TitleTarget } from "./terminal.ts";
79
- export {
80
- DONE_SYMBOL,
81
- formatTitle,
82
- signalBell,
83
- signalDone,
84
- signalWaiting,
85
- WAITING_SYMBOL,
86
- } from "./terminal.ts";
87
- export type { SuiPiToolPromptSurface, SuiPiToolSpec, ToolExecuteFn } from "./tool-framework.ts";
88
- export {
89
- CharacterParam,
90
- derivePromptSurface,
91
- FileParam,
92
- LineParam,
93
- MaxResultsParam,
94
- registerSuiPiTools,
95
- SymbolParam,
96
- } from "./tool-framework.ts";
9
+ // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
10
+ export * from "./config.ts";
11
+ // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
12
+ export * from "./context.ts";
13
+ // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
14
+ export * from "./debug-registry.ts";
15
+ // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
16
+ export * from "./path.ts";
17
+ // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
18
+ export * from "./project.ts";
19
+ // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
20
+ export * from "./session.ts";
21
+ // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
22
+ export * from "./settings.ts";
23
+ // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
24
+ export * from "./settings-ui.ts";
25
+ // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
26
+ export * from "./terminal.ts";
27
+ // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
28
+ export * from "./tool-framework.ts";
29
+ // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
30
+ export * from "./types.ts";
@@ -0,0 +1,2 @@
1
+ // supi-core path domain — file and URI path utilities.
2
+ export { fileToUri, resolveToolPath, stripToolPathPrefix, uriToFile } from "./path-utils.ts";
@@ -0,0 +1,15 @@
1
+ // supi-core project domain — project root discovery and traversal.
2
+ export type { KnownRootEntry } from "./project-roots.ts";
3
+ export {
4
+ buildKnownRootsMap,
5
+ byPathDepth,
6
+ dedupeTopmostRoots,
7
+ findProjectRoot,
8
+ isWithin,
9
+ isWithinOrEqual,
10
+ mergeKnownRoots,
11
+ resolveKnownRoot,
12
+ segmentCount,
13
+ sortRootsBySpecificity,
14
+ walkProject,
15
+ } from "./project-roots.ts";
@@ -0,0 +1,4 @@
1
+ // supi-core session domain — session utilities and registries.
2
+
3
+ export { createRegistry, createSessionStateRegistry } from "./registry-utils.ts";
4
+ export { getActiveBranchEntries } from "./session-utils.ts";
@@ -0,0 +1,2 @@
1
+ // supi-core settings-ui domain — settings TUI components (imports pi-tui at runtime, heavy).
2
+ export { createInputSubmenu, openSettingsOverlay } from "./settings/settings-ui.ts";
@@ -0,0 +1,9 @@
1
+ // supi-core settings domain — settings registry (lightweight, type-only pi-tui import).
2
+
3
+ export { registerSettingsCommand } from "./settings/settings-command.ts";
4
+ export type { SettingsScope, SettingsSection } from "./settings/settings-registry.ts";
5
+ export {
6
+ clearRegisteredSettings,
7
+ getRegisteredSettings,
8
+ registerSettings,
9
+ } from "./settings/settings-registry.ts";
@@ -0,0 +1,11 @@
1
+ /** 0-based position used by LSP and code-intelligence internally. */
2
+ export interface CodePosition {
3
+ line: number;
4
+ character: number;
5
+ }
6
+
7
+ /** Normalized location — flat replacement for LSP's nested Location/range shape. */
8
+ export interface CodeLocation {
9
+ uri: string;
10
+ range: { start: CodePosition; end: CodePosition };
11
+ }
@@ -0,0 +1,2 @@
1
+ // supi-core types domain — shared substrate types (zero dependencies, pure types).
2
+ export type { CodeLocation, CodePosition } from "./substrate-types.ts";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-ask-user",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "SuPi ask-user extension — rich questionnaire UI for structured agent-user decisions",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -21,7 +21,7 @@
21
21
  ],
22
22
  "main": "src/api.ts",
23
23
  "dependencies": {
24
- "@mrclrchtr/supi-core": "1.7.0"
24
+ "@mrclrchtr/supi-core": "1.8.0"
25
25
  },
26
26
  "bundledDependencies": [
27
27
  "@mrclrchtr/supi-core"
package/src/ask-user.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
- import { formatTitle, signalWaiting } from "@mrclrchtr/supi-core/api";
2
+ import { formatTitle, signalWaiting } from "@mrclrchtr/supi-core/terminal";
3
3
  import { AskUserValidationError, normalizeQuestionnaire } from "./normalize.ts";
4
4
  import { type AskUserToolResult, buildErrorResult, buildResult } from "./render/result.ts";
5
5
  import { renderAskUserCall, renderAskUserResult } from "./render/transcript.ts";
package/src/schema.ts CHANGED
@@ -3,101 +3,92 @@
3
3
  import { type Static, Type } from "typebox";
4
4
 
5
5
  const OptionSchema = Type.Object({
6
- value: Type.String({ description: "Stable identifier returned when this option is selected" }),
7
- label: Type.String({ description: "Display label shown to the user" }),
6
+ value: Type.String({ description: "Returned id" }),
7
+ label: Type.String({ description: "Displayed label" }),
8
8
  description: Type.Optional(
9
9
  Type.String({
10
- description: "Optional clarification shown under the label in richer UIs",
10
+ description: "Optional note",
11
11
  }),
12
12
  ),
13
13
  preview: Type.Optional(
14
14
  Type.String({
15
- description: "Optional preview content shown for the currently focused option",
15
+ description: "Optional preview",
16
16
  }),
17
17
  ),
18
18
  });
19
19
 
20
20
  const ChoiceQuestionSchema = Type.Object({
21
21
  type: Type.Literal("choice"),
22
- id: Type.String({ description: "Unique question id within this form" }),
23
- header: Type.String({ description: "Short label describing the decision" }),
24
- prompt: Type.String({ description: "Full question text shown to the user" }),
22
+ id: Type.String({ description: "Question id" }),
23
+ header: Type.String({ description: "Short label" }),
24
+ prompt: Type.String({ description: "Question text" }),
25
25
  options: Type.Array(OptionSchema, {
26
- description: "Allowed answers (2-12 distinct options)",
26
+ description: "Allowed options (2-12)",
27
27
  }),
28
28
  required: Type.Optional(
29
29
  Type.Boolean({
30
30
  default: true,
31
- description: "Whether this question must be answered for a full submit",
31
+ description: "Required for full submit",
32
32
  }),
33
33
  ),
34
34
  multi: Type.Optional(
35
35
  Type.Boolean({
36
36
  default: false,
37
- description: "Allow selecting multiple options instead of one",
37
+ description: "Allow multiple selections",
38
38
  }),
39
39
  ),
40
40
  allowOther: Type.Optional(
41
41
  Type.Boolean({
42
- description:
43
- "Allow a custom freeform answer instead of the listed options. Only valid for single-select choice questions.",
42
+ description: "Allow a custom option; single-select only",
44
43
  }),
45
44
  ),
46
45
  recommendation: Type.Optional(
47
46
  Type.Union([Type.String(), Type.Array(Type.String())], {
48
- description:
49
- "Recommended option value or values. Use a string for single-select and an array for multi-select.",
47
+ description: "Recommended value(s)",
50
48
  }),
51
49
  ),
52
50
  initial: Type.Optional(
53
51
  Type.Union([Type.String(), Type.Array(Type.String())], {
54
- description:
55
- "Initial selected option value or values. Use a string for single-select and an array for multi-select.",
52
+ description: "Initial value(s)",
56
53
  }),
57
54
  ),
58
55
  });
59
56
 
60
57
  const TextQuestionSchema = Type.Object({
61
58
  type: Type.Literal("text"),
62
- id: Type.String({ description: "Unique question id within this form" }),
63
- header: Type.String({ description: "Short label describing the prompt" }),
64
- prompt: Type.String({ description: "Full question text shown to the user" }),
59
+ id: Type.String({ description: "Question id" }),
60
+ header: Type.String({ description: "Short label" }),
61
+ prompt: Type.String({ description: "Question text" }),
65
62
  required: Type.Optional(
66
63
  Type.Boolean({
67
64
  default: true,
68
- description: "Whether this question must be answered for a full submit",
65
+ description: "Required for full submit",
69
66
  }),
70
67
  ),
71
- initial: Type.Optional(Type.String({ description: "Initial value shown in the editor" })),
72
- placeholder: Type.Optional(
73
- Type.String({ description: "Placeholder shown before the user types" }),
74
- ),
68
+ initial: Type.Optional(Type.String({ description: "Initial text" })),
69
+ placeholder: Type.Optional(Type.String({ description: "Editor placeholder" })),
75
70
  });
76
71
 
77
72
  const QuestionSchema = Type.Union([ChoiceQuestionSchema, TextQuestionSchema]);
78
73
 
79
74
  export const AskUserParamsSchema = Type.Object({
80
- title: Type.Optional(
81
- Type.String({ description: "Optional short title explaining the overall decision" }),
82
- ),
75
+ title: Type.Optional(Type.String({ description: "Optional title" })),
83
76
  intro: Type.Optional(
84
77
  Type.String({
85
- description: "Optional introductory context explaining why the agent is asking",
78
+ description: "Optional intro for the decision",
86
79
  }),
87
80
  ),
88
81
  questions: Type.Array(QuestionSchema, {
89
- description: "Between 1 and 4 focused questions that belong to the same decision",
82
+ description: "1-4 focused questions for one decision",
90
83
  }),
91
84
  allowPartialSubmit: Type.Optional(
92
85
  Type.Boolean({
93
- description:
94
- "Allow the user to submit a partial form when some required questions remain unanswered",
86
+ description: "Allow partial submission",
95
87
  }),
96
88
  ),
97
89
  allowDiscuss: Type.Optional(
98
90
  Type.Boolean({
99
- description:
100
- "Allow the user to switch back into discussion instead of committing to a final answer",
91
+ description: "Allow discussion handoff",
101
92
  }),
102
93
  ),
103
94
  });
@@ -1,15 +1,13 @@
1
1
  // Prompt guidance and tool description for the redesigned ask_user tool.
2
2
 
3
3
  export const toolDescription =
4
- "Ask the user for a focused blocking decision when explicit input is required to proceed safely. Requires an interactive UI with custom overlay support, and only one ask_user form can be active at a time. Use 1-4 related `choice` or `text` questions. Do not use ask_user for open-ended interviews or repo facts you can get yourself. Forms may allow partial submit or discussion handoff.";
4
+ "Ask the user for a focused blocking decision. Use 1-4 related `choice` or `text` questions, and keep only one ask_user form active at a time.";
5
5
 
6
6
  export const promptSnippet = "ask_user — request a focused blocking user decision";
7
7
 
8
8
  export const promptGuidelines = [
9
- "Use ask_user only when explicit user input is required to proceed safely; do not use ask_user for open-ended interviews or repo facts.",
9
+ "Use ask_user only for blocking user input, not open-ended interviews or repo facts.",
10
10
  "Use ask_user with 1-4 related questions; prefer one when possible.",
11
- 'Use ask_user `choice` for fixed options and ask_user `text` for freeform input; model yes/no as `choice` with `{ value: "yes", label: "Yes" }` and `{ value: "no", label: "No" }`.',
12
- "Use ask_user `allowOther` only on single-select `choice` questions.",
13
- "Use ask_user `allowDiscuss` or `allowPartialSubmit` only when that outcome is actionable.",
14
- "Do not call ask_user while another ask_user form is already in flight.",
11
+ "Use ask_user `choice` for fixed options and ask_user `text` for freeform input; yes/no should be a `choice`.",
12
+ "Keep one ask_user form active at a time; use `allowOther` only for single-select choice and `allowDiscuss`/`allowPartialSubmit` only when actionable.",
15
13
  ];
@@ -5,6 +5,7 @@ import {
5
5
  Markdown,
6
6
  type SelectList,
7
7
  type SelectListTheme,
8
+ wrapTextWithAnsi,
8
9
  } from "@earendil-works/pi-tui";
9
10
  import type { AskUserController } from "../session/controller.ts";
10
11
  import type { NormalizedQuestionnaire } from "../types.ts";
@@ -100,11 +101,15 @@ export function clampIndex(index: number, length: number): number {
100
101
  function renderHeader(args: RenderOverlayFrameArgs): string[] {
101
102
  const lines: string[] = [];
102
103
  const { title, intro } = args.controller.questionnaire;
103
- if (title) lines.push(args.theme.fg("accent", args.theme.bold(title)));
104
+ if (title)
105
+ lines.push(...wrapTextWithAnsi(args.theme.fg("accent", args.theme.bold(title)), args.width));
104
106
  lines.push(
105
- args.theme.fg(
106
- "muted",
107
- `${args.controller.currentIndex + 1}/${args.controller.questions.length} · ${args.controller.currentQuestion.header}`,
107
+ ...wrapTextWithAnsi(
108
+ args.theme.fg(
109
+ "muted",
110
+ `${args.controller.currentIndex + 1}/${args.controller.questions.length} · ${args.controller.currentQuestion.header}`,
111
+ ),
112
+ args.width,
108
113
  ),
109
114
  );
110
115
  if (intro) {
@@ -197,14 +202,17 @@ function renderEditorLines(args: RenderOverlayFrameArgs, width: number): string[
197
202
  : "Option note"
198
203
  : "Your answer";
199
204
 
200
- const lines = [args.theme.fg("accent", label), ...args.editor.render(Math.max(20, width - 1))];
205
+ const labelLines = wrapTextWithAnsi(args.theme.fg("accent", label), width);
206
+ const lines = [...labelLines, ...args.editor.render(Math.max(20, width - 1))];
201
207
  const question = args.controller.currentQuestion;
202
208
  if (question.type !== "text" || args.editor.getText()) return lines;
203
209
 
204
210
  if (question.initial) {
205
- lines.push(args.theme.fg("dim", `Initial: ${question.initial}`));
211
+ lines.push(...wrapTextWithAnsi(args.theme.fg("dim", `Initial: ${question.initial}`), width));
206
212
  } else if (question.placeholder) {
207
- lines.push(args.theme.fg("dim", `Placeholder: ${question.placeholder}`));
213
+ lines.push(
214
+ ...wrapTextWithAnsi(args.theme.fg("dim", `Placeholder: ${question.placeholder}`), width),
215
+ );
208
216
  }
209
217
  return lines;
210
218
  }
@@ -177,7 +177,7 @@ function renderOptionRow(args: {
177
177
  : `${prefix}${labelText}`;
178
178
  const noteSuffix = hasNote ? ` ${theme.fg("accent", "[note]")}` : "";
179
179
 
180
- const lines: string[] = [`${baseText}${noteSuffix}`];
180
+ const lines: string[] = wrapTextWithAnsi(`${baseText}${noteSuffix}`, width);
181
181
 
182
182
  if (option.description) {
183
183
  const descWidth = Math.max(10, width - 2);