hotmilk 0.1.13 → 0.1.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.
Files changed (57) hide show
  1. package/AGENTS.md +12 -10
  2. package/README.md +237 -53
  3. package/agents/README.md +3 -3
  4. package/agents/assistant.md +1 -1
  5. package/agents/coach.md +26 -4
  6. package/agents/designer.md +1 -1
  7. package/agents/planner.md +2 -2
  8. package/hotmilk.json +11 -0
  9. package/package.json +43 -24
  10. package/prompts/prompt-eval.md +241 -0
  11. package/prompts/tidy.md +3 -1
  12. package/prompts/translate.md +3 -1
  13. package/skills/make-docs/SKILL.md +43 -0
  14. package/skills/pioneer/SKILL.md +135 -131
  15. package/skills/pioneer/references/autoresearch-routing.md +114 -0
  16. package/skills/pioneer/references/chat-plan.md +50 -0
  17. package/skills/pioneer/references/goal-gate.md +54 -0
  18. package/skills/pioneer/references/graph-recon-gate.md +50 -0
  19. package/skills/pioneer/references/observational-memory-routing.md +90 -0
  20. package/skills/pioneer/references/openspec-routing.md +50 -0
  21. package/skills/pioneer/references/plannotator-routing.md +83 -0
  22. package/skills/pioneer/references/prompt-eval-gate.md +38 -0
  23. package/skills/pioneer/references/shazam-routing.md +58 -0
  24. package/skills/recommend-research/SKILL.md +94 -6
  25. package/skills/recommend-research/references/example-narrow.md +53 -0
  26. package/skills/recommend-research/references/scope-gate.md +46 -0
  27. package/skills/recommend-research/references/shortlist-gate.md +69 -0
  28. package/skills/recommend-research/references/verify-gate.md +73 -0
  29. package/skills/update-docs/SKILL.md +86 -0
  30. package/skills/update-docs/references/doc-inventory.md +69 -0
  31. package/skills/update-docs/references/drift-verification.md +59 -0
  32. package/src/bootstrap/btw.ts +49 -0
  33. package/src/bootstrap/context-stack.ts +71 -14
  34. package/src/bootstrap/dashboard-settings.ts +16 -2
  35. package/src/bootstrap/dashboard.ts +85 -10
  36. package/src/bootstrap/defaults.ts +26 -0
  37. package/src/bootstrap/extensions.ts +10 -1
  38. package/src/bootstrap/global-extension-sources.ts +43 -4
  39. package/src/bootstrap/graph.ts +12 -0
  40. package/src/bootstrap/project-trust.ts +55 -0
  41. package/src/bootstrap/resolve-bundled.ts +28 -4
  42. package/src/bootstrap/session.ts +61 -1
  43. package/src/bootstrap/subagents-doctor.ts +12 -0
  44. package/src/config/bundled-extensions.ts +42 -0
  45. package/src/config/bundled-package-registry.ts +3 -0
  46. package/src/config/hotmilk.ts +76 -1
  47. package/src/config/mcp.ts +45 -12
  48. package/src/config/resolve.ts +21 -0
  49. package/src/config/runtime.ts +6 -0
  50. package/src/controller/input.ts +15 -0
  51. package/src/controller/mode.ts +32 -0
  52. package/src/index.ts +5 -0
  53. package/src/ui/footer.ts +63 -1
  54. package/src/ui/github-user.ts +52 -4
  55. package/src/ui/session-logo.ts +31 -3
  56. package/themes/monokai.json +1 -1
  57. package/skills/empirical-prompt-tuning/SKILL.md +0 -232
@@ -73,12 +73,30 @@ export const BUNDLED_EXTENSION_DEFINITIONS = [
73
73
  module: "graphify-pi/extensions/graphify.ts",
74
74
  group: "Agent tools",
75
75
  },
76
+ {
77
+ id: "shazam",
78
+ package: { packageName: "pi-shazam" },
79
+ module: "pi-shazam/dist/index.js",
80
+ group: "Agent tools",
81
+ },
82
+ {
83
+ id: "prompt-template-model",
84
+ package: { packageName: "pi-prompt-template-model" },
85
+ module: "pi-prompt-template-model/index.ts",
86
+ group: "Agent tools",
87
+ },
76
88
  {
77
89
  id: "subagents",
78
90
  package: { packageName: "pi-subagents" },
79
91
  module: "pi-subagents/src/extension/index.ts",
80
92
  group: "Agent tools",
81
93
  },
94
+ {
95
+ id: "pi-actors",
96
+ package: { packageName: "@llblab/pi-actors" },
97
+ module: "@llblab/pi-actors/index.ts",
98
+ group: "Agent tools",
99
+ },
82
100
  {
83
101
  id: "goal",
84
102
  package: { packageName: "pi-goal" },
@@ -116,6 +134,18 @@ export const BUNDLED_EXTENSION_DEFINITIONS = [
116
134
  group: "Context & performance",
117
135
  loadPhase: "context-stack",
118
136
  },
137
+ {
138
+ id: "observational-memory",
139
+ package: { packageName: "pi-observational-memory" },
140
+ module: "pi-observational-memory/src/index.ts",
141
+ group: "Context & performance",
142
+ },
143
+ {
144
+ id: "supi-context",
145
+ package: { packageName: "@mrclrchtr/supi-context" },
146
+ module: "@mrclrchtr/supi-context/src/extension.ts",
147
+ group: "Context & performance",
148
+ },
119
149
  {
120
150
  id: "mcp-adapter",
121
151
  package: { packageName: "pi-mcp-adapter" },
@@ -128,6 +158,12 @@ export const BUNDLED_EXTENSION_DEFINITIONS = [
128
158
  module: "@tomxprime/planning-with-files/extensions/planning-with-files/index.ts",
129
159
  group: "Workflow",
130
160
  },
161
+ {
162
+ id: "plannotator",
163
+ package: { packageName: "@plannotator/pi-extension" },
164
+ module: "@plannotator/pi-extension/index.ts",
165
+ group: "Workflow",
166
+ },
131
167
  {
132
168
  id: "caveman",
133
169
  package: { packageName: "pi-caveman" },
@@ -140,6 +176,12 @@ export const BUNDLED_EXTENSION_DEFINITIONS = [
140
176
  module: "pi-red-green/dist/index.js",
141
177
  group: "Workflow",
142
178
  },
179
+ {
180
+ id: "autoresearch",
181
+ package: { packageName: "pi-autoresearch" },
182
+ module: "pi-autoresearch/extensions/pi-autoresearch/index.ts",
183
+ group: "Experiments",
184
+ },
143
185
  {
144
186
  id: "agent-dashboard",
145
187
  package: {
@@ -8,13 +8,16 @@ export type { BundledPackageSpec };
8
8
 
9
9
  export { BUNDLED_EXTENSION_PACKAGES };
10
10
 
11
+ /** Canonical npm package name for hotmilk itself. */
11
12
  export const HOTMILK_PACKAGE_NAME = "hotmilk";
12
13
 
14
+ /** Return all package names (primary + aliases) that satisfy a bundled extension id. */
13
15
  export function packageNamesForBundledExtension(id: BundledExtensionId): readonly string[] {
14
16
  const spec = BUNDLED_EXTENSION_PACKAGES[id];
15
17
  return spec.aliases ? [spec.packageName, ...spec.aliases] : [spec.packageName];
16
18
  }
17
19
 
20
+ /** Return the installed package name that provides a bundled id globally, if any. */
18
21
  export function detectGlobalProviderForBundledExtension(
19
22
  id: BundledExtensionId,
20
23
  installedPackageNames: ReadonlySet<string>,
@@ -1,3 +1,10 @@
1
+ /**
2
+ * hotmilk user configuration loading and defaults.
3
+ *
4
+ * Reads/writes `~/.pi/agent/hotmilk.json` and derives the in-memory default
5
+ * config from the bundled `hotmilk.json` template.
6
+ */
7
+
1
8
  import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
9
  import { homedir } from "node:os";
3
10
  import { dirname, join } from "node:path";
@@ -16,11 +23,16 @@ function readBundledHotmilkTemplate(): HotmilkConfig {
16
23
  return JSON.parse(readFileSync(BUNDLED_TEMPLATE_PATH, "utf8")) as HotmilkConfig;
17
24
  }
18
25
 
26
+ function isProjectTrustMode(value: unknown): value is ProjectTrustMode {
27
+ return value === "delegate" || value === "prompt" || value === "always" || value === "never";
28
+ }
29
+
19
30
  function buildDefaultConfigFromTemplate(template: HotmilkConfig): {
20
31
  extensions: Record<BundledExtensionId, boolean>;
21
32
  graph: ResolvedGraphSettings;
22
33
  defaults: ResolvedDefaults;
23
34
  mcp: ResolvedMcpSettings;
35
+ projectTrust: ResolvedProjectTrust;
24
36
  } {
25
37
  const extensions = {} as Record<BundledExtensionId, boolean>;
26
38
  for (const id of BUNDLED_EXTENSION_IDS) {
@@ -33,6 +45,7 @@ function buildDefaultConfigFromTemplate(template: HotmilkConfig): {
33
45
 
34
46
  const language = template.defaults?.language?.trim();
35
47
  const persona = template.defaults?.persona;
48
+ const projectTrustMode = template.projectTrust?.mode;
36
49
 
37
50
  return {
38
51
  extensions,
@@ -47,16 +60,33 @@ function buildDefaultConfigFromTemplate(template: HotmilkConfig): {
47
60
  mcp: {
48
61
  seedOnStart: template.mcp?.seedOnStart ?? false,
49
62
  },
63
+ projectTrust: {
64
+ mode: isProjectTrustMode(projectTrustMode) ? projectTrustMode : "delegate",
65
+ remember: template.projectTrust?.remember ?? false,
66
+ },
50
67
  };
51
68
  }
52
69
 
70
+ /** Filename used for hotmilk config under the config root. */
53
71
  export const CONFIG_FILENAME = "hotmilk.json";
72
+ /** Human-readable label for the agent-level config path. */
54
73
  export const AGENT_HOTMILK_CONFIG_LABEL = "~/.pi/agent/hotmilk.json";
55
74
 
56
75
  export { BUNDLED_EXTENSION_IDS, type BundledExtensionId } from "./bundled-extensions.ts";
57
76
 
77
+ /** Supported persona modes. */
58
78
  export type PersonaMode = "gentleman" | "neutral";
59
79
 
80
+ /** Supported project-trust decision modes. */
81
+ export type ProjectTrustMode = "delegate" | "prompt" | "always" | "never";
82
+
83
+ /** Resolved project-trust settings. */
84
+ export type ResolvedProjectTrust = {
85
+ mode: ProjectTrustMode;
86
+ remember: boolean;
87
+ };
88
+
89
+ /** User-editable hotmilk config schema. */
60
90
  export type HotmilkConfig = {
61
91
  extensions?: Partial<Record<BundledExtensionId, boolean>>;
62
92
  graph?: {
@@ -70,6 +100,10 @@ export type HotmilkConfig = {
70
100
  mcp?: {
71
101
  seedOnStart?: boolean;
72
102
  };
103
+ projectTrust?: {
104
+ mode?: ProjectTrustMode;
105
+ remember?: boolean;
106
+ };
73
107
  };
74
108
 
75
109
  type ConfigPathResult = {
@@ -85,23 +119,36 @@ type SeedHotmilkConfigResult = ConfigPathResult & {
85
119
  seeded: boolean;
86
120
  };
87
121
 
122
+ /** Resolved graphify-related settings. */
88
123
  export type ResolvedGraphSettings = {
89
124
  warnOnStale: boolean;
90
125
  autoSuggestUpdate: boolean;
91
126
  };
92
127
 
128
+ /** Resolved default persona/language settings. */
93
129
  export type ResolvedDefaults = {
94
130
  language?: string;
95
131
  persona: PersonaMode;
96
132
  };
97
133
 
134
+ /** Resolved MCP bootstrap settings. */
98
135
  export type ResolvedMcpSettings = {
99
136
  seedOnStart: boolean;
100
137
  };
101
138
 
102
- /** Derived from the bundled `hotmilk.json` template (single source of truth). */
139
+ /**
140
+ * In-memory default config derived from the bundled `hotmilk.json` template.
141
+ *
142
+ * This is the single source of truth for bundled defaults.
143
+ */
103
144
  export const DEFAULT_HOTMILK_CONFIG = buildDefaultConfigFromTemplate(readBundledHotmilkTemplate());
104
145
 
146
+ /**
147
+ * Resolve the directory that holds `hotmilk.json`.
148
+ *
149
+ * @param configRoot - explicit override
150
+ * @returns config root directory
151
+ */
105
152
  export function resolveHotmilkConfigRoot(configRoot?: string): string {
106
153
  if (configRoot) {
107
154
  return configRoot;
@@ -112,6 +159,11 @@ export function resolveHotmilkConfigRoot(configRoot?: string): string {
112
159
  return join(homedir(), ".pi", "agent");
113
160
  }
114
161
 
162
+ /**
163
+ * Resolve the full path to `hotmilk.json`.
164
+ *
165
+ * @param configRoot - explicit config root override
166
+ */
115
167
  export function getHotmilkConfigPath(configRoot?: string): string {
116
168
  return join(resolveHotmilkConfigRoot(configRoot), CONFIG_FILENAME);
117
169
  }
@@ -129,6 +181,12 @@ function writeHotmilkConfigFile(path: string, config: HotmilkConfig): void {
129
181
  writeFileSync(path, `${JSON.stringify(config, null, 2)}\n`, "utf8");
130
182
  }
131
183
 
184
+ /**
185
+ * Copy the bundled `hotmilk.json` template to the agent config directory.
186
+ *
187
+ * @param configRoot - explicit config root override
188
+ * @returns seed result, with `error` set on filesystem failure
189
+ */
132
190
  export function seedHotmilkConfigIfMissing(configRoot?: string): SeedHotmilkConfigResult {
133
191
  const configPath = getHotmilkConfigPath(configRoot);
134
192
  if (existsSync(configPath)) {
@@ -139,6 +197,11 @@ export function seedHotmilkConfigIfMissing(configRoot?: string): SeedHotmilkConf
139
197
  return { seeded: true, path: configPath };
140
198
  }
141
199
 
200
+ /**
201
+ * Load hotmilk config from disk, falling back to {@link DEFAULT_HOTMILK_CONFIG}.
202
+ *
203
+ * @param configRoot - explicit config root override
204
+ */
142
205
  export function loadHotmilkConfig(configRoot?: string): LoadedHotmilkConfig {
143
206
  const configPath = getHotmilkConfigPath(configRoot);
144
207
  if (!existsSync(configPath)) {
@@ -153,6 +216,12 @@ export function loadHotmilkConfig(configRoot?: string): LoadedHotmilkConfig {
153
216
  }
154
217
  }
155
218
 
219
+ /**
220
+ * Persist hotmilk config to disk.
221
+ *
222
+ * @param config - config object to write
223
+ * @param configRoot - explicit config root override
224
+ */
156
225
  export function saveHotmilkConfig(config: HotmilkConfig, configRoot?: string): ConfigPathResult {
157
226
  const configPath = getHotmilkConfigPath(configRoot);
158
227
  try {
@@ -163,6 +232,11 @@ export function saveHotmilkConfig(config: HotmilkConfig, configRoot?: string): C
163
232
  }
164
233
  }
165
234
 
235
+ /**
236
+ * Load and resolve bundled-extension toggle state.
237
+ *
238
+ * @param configRoot - explicit config root override
239
+ */
166
240
  export function loadBundledExtensionToggles(
167
241
  configRoot?: string,
168
242
  ): Record<BundledExtensionId, boolean> {
@@ -174,4 +248,5 @@ export {
174
248
  resolveDefaults,
175
249
  resolveGraphSettings,
176
250
  resolveMcpSettings,
251
+ resolveProjectTrust,
177
252
  } from "./resolve.ts";
package/src/config/mcp.ts CHANGED
@@ -1,3 +1,11 @@
1
+ /**
2
+ * MCP config seeding and legacy context-mode cleanup.
3
+ *
4
+ * Seeds the agent `mcp.json` from the bundled template and prunes duplicate
5
+ * `context-mode` MCP server entries because context-mode exposes `ctx_*`
6
+ * through its own extension bridge.
7
+ */
8
+
1
9
  import { getAgentDir } from "@earendil-works/pi-coding-agent";
2
10
  import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
11
  import { dirname, join } from "node:path";
@@ -11,6 +19,11 @@ type AgentMcpFile = {
11
19
  mcpServers?: Record<string, unknown>;
12
20
  };
13
21
 
22
+ /**
23
+ * Copy the bundled `mcp.json` template into the agent directory when absent.
24
+ *
25
+ * @returns seed result
26
+ */
14
27
  export function seedAgentMcpJsonIfMissing(): { seeded: boolean; path: string } {
15
28
  const agentMcpPath = join(getAgentDir(), "mcp.json");
16
29
  if (existsSync(agentMcpPath)) {
@@ -25,29 +38,49 @@ export function seedAgentMcpJsonIfMissing(): { seeded: boolean; path: string } {
25
38
  }
26
39
 
27
40
  /**
28
- * Option A: context-mode extension registers ctx_* via its built-in MCP bridge.
29
- * A hotmilk-seeded `context-mode` MCP server duplicates that path via pi-mcp-adapter.
41
+ * Remove the legacy `context-mode` MCP server entry from a given `mcp.json`.
42
+ *
43
+ * Context-mode exposes `ctx_*` through its own extension bridge, so a seeded
44
+ * MCP server for it would duplicate functionality.
45
+ *
46
+ * @param mcpJsonPath - path to the MCP config file
47
+ * @returns prune result, with `error` set on filesystem failure
30
48
  */
31
- export function pruneContextModeFromMcpJsonAt(mcpJsonPath: string): boolean {
49
+ export function pruneContextModeFromMcpJsonAt(mcpJsonPath: string): {
50
+ pruned: boolean;
51
+ path: string;
52
+ error?: string;
53
+ } {
32
54
  if (!existsSync(mcpJsonPath)) {
33
- return false;
55
+ return { pruned: false, path: mcpJsonPath };
34
56
  }
35
57
 
36
58
  try {
37
59
  const parsed = JSON.parse(readFileSync(mcpJsonPath, "utf8")) as AgentMcpFile;
38
60
  if (!parsed.mcpServers?.[CONTEXT_MODE_MCP_SERVER_ID]) {
39
- return false;
61
+ return { pruned: false, path: mcpJsonPath };
40
62
  }
41
63
  delete parsed.mcpServers[CONTEXT_MODE_MCP_SERVER_ID];
42
64
  writeFileSync(mcpJsonPath, `${JSON.stringify(parsed, null, 2)}\n`, "utf8");
43
- return true;
44
- } catch {
45
- return false;
65
+ return { pruned: true, path: mcpJsonPath };
66
+ } catch (error) {
67
+ return {
68
+ pruned: false,
69
+ path: mcpJsonPath,
70
+ error: error instanceof Error ? error.message : String(error),
71
+ };
46
72
  }
47
73
  }
48
74
 
49
- export function pruneContextModeMcpServerFromAgentConfig(): { pruned: boolean; path: string } {
50
- const agentMcpPath = join(getAgentDir(), "mcp.json");
51
- const pruned = pruneContextModeFromMcpJsonAt(agentMcpPath);
52
- return { pruned, path: agentMcpPath };
75
+ /**
76
+ * Prune legacy `context-mode` MCP server entry from the agent `mcp.json`.
77
+ *
78
+ * @returns prune result
79
+ */
80
+ export function pruneContextModeMcpServerFromAgentConfig(): {
81
+ pruned: boolean;
82
+ path: string;
83
+ error?: string;
84
+ } {
85
+ return pruneContextModeFromMcpJsonAt(join(getAgentDir(), "mcp.json"));
53
86
  }
@@ -4,15 +4,18 @@ import {
4
4
  type BundledExtensionId,
5
5
  type HotmilkConfig,
6
6
  type PersonaMode,
7
+ type ProjectTrustMode,
7
8
  type ResolvedDefaults,
8
9
  type ResolvedGraphSettings,
9
10
  type ResolvedMcpSettings,
11
+ type ResolvedProjectTrust,
10
12
  } from "./hotmilk.ts";
11
13
 
12
14
  function isPersonaMode(value: unknown): value is PersonaMode {
13
15
  return value === "neutral" || value === "gentleman";
14
16
  }
15
17
 
18
+ /** Resolve final bundled-extension toggles by overlaying user config on bundled defaults. */
16
19
  export function resolveBundledExtensionToggles(
17
20
  config: HotmilkConfig,
18
21
  ): Record<BundledExtensionId, boolean> {
@@ -24,6 +27,7 @@ export function resolveBundledExtensionToggles(
24
27
  ) as Record<BundledExtensionId, boolean>;
25
28
  }
26
29
 
30
+ /** Resolve graphify settings from user config with bundled defaults fallback. */
27
31
  export function resolveGraphSettings(config: HotmilkConfig): ResolvedGraphSettings {
28
32
  return {
29
33
  warnOnStale: config.graph?.warnOnStale ?? DEFAULT_HOTMILK_CONFIG.graph.warnOnStale,
@@ -32,6 +36,7 @@ export function resolveGraphSettings(config: HotmilkConfig): ResolvedGraphSettin
32
36
  };
33
37
  }
34
38
 
39
+ /** Resolve default runtime settings (language, persona) from user config. */
35
40
  export function resolveDefaults(config: HotmilkConfig): ResolvedDefaults {
36
41
  const language = config.defaults?.language?.trim();
37
42
  const persona = config.defaults?.persona;
@@ -41,8 +46,24 @@ export function resolveDefaults(config: HotmilkConfig): ResolvedDefaults {
41
46
  };
42
47
  }
43
48
 
49
+ /** Resolve MCP settings from user config with bundled defaults fallback. */
44
50
  export function resolveMcpSettings(config: HotmilkConfig): ResolvedMcpSettings {
45
51
  return {
46
52
  seedOnStart: config.mcp?.seedOnStart ?? DEFAULT_HOTMILK_CONFIG.mcp.seedOnStart,
47
53
  };
48
54
  }
55
+
56
+ function isProjectTrustMode(value: unknown): value is ProjectTrustMode {
57
+ return value === "delegate" || value === "prompt" || value === "always" || value === "never";
58
+ }
59
+
60
+ /** Resolve project-trust settings from user config with bundled defaults fallback. */
61
+ export function resolveProjectTrust(config: HotmilkConfig): ResolvedProjectTrust {
62
+ const mode = config.projectTrust?.mode;
63
+ const remember = config.projectTrust?.remember;
64
+ return {
65
+ mode: isProjectTrustMode(mode) ? mode : DEFAULT_HOTMILK_CONFIG.projectTrust.mode,
66
+ remember:
67
+ typeof remember === "boolean" ? remember : DEFAULT_HOTMILK_CONFIG.projectTrust.remember,
68
+ };
69
+ }
@@ -6,14 +6,17 @@ import {
6
6
  type ResolvedDefaults,
7
7
  type ResolvedGraphSettings,
8
8
  type ResolvedMcpSettings,
9
+ type ResolvedProjectTrust,
9
10
  } from "./hotmilk.ts";
10
11
  import {
11
12
  resolveBundledExtensionToggles,
12
13
  resolveDefaults,
13
14
  resolveGraphSettings,
14
15
  resolveMcpSettings,
16
+ resolveProjectTrust,
15
17
  } from "./resolve.ts";
16
18
 
19
+ /** Aggregated hotmilk configuration and resolved runtime settings. */
17
20
  export type HotmilkRuntime = {
18
21
  config: HotmilkConfig;
19
22
  configPath: string;
@@ -24,8 +27,10 @@ export type HotmilkRuntime = {
24
27
  defaults: ResolvedDefaults;
25
28
  graph: ResolvedGraphSettings;
26
29
  mcp: ResolvedMcpSettings;
30
+ projectTrust: ResolvedProjectTrust;
27
31
  };
28
32
 
33
+ /** Load hotmilk config and resolve all runtime settings into a single object. */
29
34
  export function createHotmilkRuntime(configRoot?: string): HotmilkRuntime {
30
35
  const loaded = loadHotmilkConfig(configRoot);
31
36
  return {
@@ -37,5 +42,6 @@ export function createHotmilkRuntime(configRoot?: string): HotmilkRuntime {
37
42
  defaults: resolveDefaults(loaded.config),
38
43
  graph: resolveGraphSettings(loaded.config),
39
44
  mcp: resolveMcpSettings(loaded.config),
45
+ projectTrust: resolveProjectTrust(loaded.config),
40
46
  };
41
47
  }
@@ -1,8 +1,14 @@
1
1
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
  import { openModeSettingsModal } from "./mode.ts";
3
3
 
4
+ /** Prefix for interrupt commands. */
4
5
  const INTERRUPT_PREFIX = "/interrupt ";
5
6
 
7
+ /**
8
+ * Handle /stop command input.
9
+ *
10
+ * @param ctx - extension context
11
+ */
6
12
  function handleStopInput(ctx: ExtensionContext): void {
7
13
  if (!ctx.isIdle()) {
8
14
  ctx.abort();
@@ -12,6 +18,13 @@ function handleStopInput(ctx: ExtensionContext): void {
12
18
  ctx.ui.notify("No running work to stop.", "info");
13
19
  }
14
20
 
21
+ /**
22
+ * Handle /interrupt command input.
23
+ *
24
+ * @param interruptPrompt - interrupt prompt text
25
+ * @param pi - Pi extension API
26
+ * @param ctx - extension context
27
+ */
15
28
  function handleInterruptInput(
16
29
  interruptPrompt: string,
17
30
  pi: ExtensionAPI,
@@ -28,6 +41,7 @@ function handleInterruptInput(
28
41
  ctx.ui.notify("Interrupt prompt sent.", "info");
29
42
  }
30
43
 
44
+ /** Route typed slash commands (`/stop`, `/interrupt`, `/mode`) to their handlers. */
31
45
  export function routeInputCommand(text: string, pi: ExtensionAPI, ctx: ExtensionContext): void {
32
46
  const normalizedText = text.trim();
33
47
  if (normalizedText === "/stop") {
@@ -41,6 +55,7 @@ export function routeInputCommand(text: string, pi: ExtensionAPI, ctx: Extension
41
55
  }
42
56
  }
43
57
 
58
+ /** Register `/stop`, `/interrupt`, and `/mode` as Pi commands. */
44
59
  export function registerInputCommands(pi: ExtensionAPI): void {
45
60
  pi.registerCommand("stop", {
46
61
  description: "Stop current running work.",
@@ -9,10 +9,22 @@ import {
9
9
  saveHotmilkConfig,
10
10
  } from "../config/hotmilk.ts";
11
11
 
12
+ /**
13
+ * Format toggle state as "on" or "off".
14
+ *
15
+ * @param enabled - toggle state
16
+ * @returns formatted state
17
+ */
12
18
  function formatToggleState(enabled: boolean): "on" | "off" {
13
19
  return enabled ? "on" : "off";
14
20
  }
15
21
 
22
+ /**
23
+ * Format toggle rows for notification display.
24
+ *
25
+ * @param toggles - extension toggle states
26
+ * @returns formatted toggle rows
27
+ */
16
28
  function formatToggleRows(toggles: Record<BundledExtensionId, boolean>): string {
17
29
  return BUNDLED_EXTENSION_GROUPS.map((group) => {
18
30
  const rows = group.ids.map((id) => ` ${id}: ${formatToggleState(toggles[id])}`);
@@ -20,6 +32,12 @@ function formatToggleRows(toggles: Record<BundledExtensionId, boolean>): string
20
32
  }).join("\n\n");
21
33
  }
22
34
 
35
+ /**
36
+ * Create setting items for the mode settings modal.
37
+ *
38
+ * @param toggles - extension toggle states
39
+ * @returns setting items
40
+ */
23
41
  function createModeSettingItems(toggles: Record<BundledExtensionId, boolean>): SettingItem[] {
24
42
  return BUNDLED_EXTENSION_GROUPS.flatMap((group) => [
25
43
  {
@@ -36,6 +54,12 @@ function createModeSettingItems(toggles: Record<BundledExtensionId, boolean>): S
36
54
  ]);
37
55
  }
38
56
 
57
+ /**
58
+ * Notify current config state.
59
+ *
60
+ * @param ctx - extension context
61
+ * @param toggles - extension toggle states
62
+ */
39
63
  function notifyCurrentConfig(
40
64
  ctx: ExtensionContext,
41
65
  toggles: Record<BundledExtensionId, boolean>,
@@ -43,6 +67,13 @@ function notifyCurrentConfig(
43
67
  ctx.ui.notify(`${AGENT_HOTMILK_CONFIG_LABEL}\n${formatToggleRows(toggles)}`, "info");
44
68
  }
45
69
 
70
+ /**
71
+ * Save extension toggle state to config.
72
+ *
73
+ * @param ctx - extension context
74
+ * @param extensionId - extension ID
75
+ * @param enabled - toggle state
76
+ */
46
77
  function saveExtensionToggle(
47
78
  ctx: ExtensionContext,
48
79
  extensionId: BundledExtensionId,
@@ -58,6 +89,7 @@ function saveExtensionToggle(
58
89
  }
59
90
  }
60
91
 
92
+ /** Open the interactive TUI modal for toggling bundled extensions. */
61
93
  export async function openModeSettingsModal(ctx: ExtensionContext): Promise<void> {
62
94
  const toggles = loadBundledExtensionToggles();
63
95
  const items = createModeSettingItems(toggles);
package/src/index.ts CHANGED
@@ -12,11 +12,15 @@ import { registerSessionHandlers } from "./bootstrap/session.ts";
12
12
  import { registerInputCommands, routeInputCommand } from "./controller/input.ts";
13
13
  import { registerHotmilkSessionLogo } from "./ui/session-logo.ts";
14
14
  import { installHotmilkCtxSearchCapture } from "./bootstrap/btw.ts";
15
+ import { registerProjectTrustHandlers } from "./bootstrap/project-trust.ts";
15
16
  import { registerSubagentsDoctorCommand } from "./bootstrap/subagents-doctor.ts";
16
17
 
18
+ /** Main Pi extension entry point: register all hotmilk handlers and bundled extensions. */
17
19
  export default async function registerHotmilk(pi: ExtensionAPI): Promise<void> {
18
20
  const runtime = createHotmilkRuntime();
19
21
 
22
+ registerProjectTrustHandlers(pi, runtime.projectTrust);
23
+
20
24
  // Register before bundled imports so session_start handlers exist when bindExtensions emits.
21
25
  registerHotmilkSessionLogo(pi);
22
26
  installHotmilkCtxSearchCapture(pi);
@@ -25,6 +29,7 @@ export default async function registerHotmilk(pi: ExtensionAPI): Promise<void> {
25
29
 
26
30
  const bundled = await registerBundledExtensions(pi, runtime.extensionToggles, {
27
31
  cwd: process.cwd(),
32
+ includeProjectSettings: false,
28
33
  });
29
34
  if (runtime.extensionToggles.subagents === true) {
30
35
  await registerSubagentsDoctorCommand(pi);