gentle-pi 0.3.2 → 0.3.3

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.
@@ -130,6 +130,7 @@ const SDD_AGENT_NAMES = [
130
130
  "sdd-verify",
131
131
  "sdd-archive",
132
132
  ] as const;
133
+ const SDD_AGENT_NAME_SET = new Set<string>(SDD_AGENT_NAMES);
133
134
 
134
135
  type SddAgentName = (typeof SDD_AGENT_NAMES)[number];
135
136
  type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
@@ -170,6 +171,34 @@ const MODEL_CONTROL_OPTIONS = [
170
171
  CUSTOM_MODEL,
171
172
  ] as const;
172
173
 
174
+ function readStringPath(value: unknown, path: string[]): string | undefined {
175
+ let current = value;
176
+ for (const key of path) {
177
+ if (!isRecord(current)) return undefined;
178
+ current = current[key];
179
+ }
180
+ return typeof current === "string" ? current : undefined;
181
+ }
182
+
183
+ function isSddAgentStartEvent(event: unknown): boolean {
184
+ const candidates = [
185
+ readStringPath(event, ["agentName"]),
186
+ readStringPath(event, ["agent"]),
187
+ readStringPath(event, ["name"]),
188
+ readStringPath(event, ["agent", "name"]),
189
+ readStringPath(event, ["subagent", "name"]),
190
+ ]
191
+ .filter((value): value is string => value !== undefined)
192
+ .map((value) => value.trim());
193
+ if (candidates.some((value) => SDD_AGENT_NAME_SET.has(value))) return true;
194
+
195
+ const systemPrompt = readStringPath(event, ["systemPrompt"]) ?? "";
196
+ return SDD_AGENT_NAMES.some((name) => {
197
+ const phase = name.replace(/^sdd-/, "");
198
+ return new RegExp(`\\bSDD ${phase} executor\\b`, "i").test(systemPrompt);
199
+ });
200
+ }
201
+
173
202
  function evaluateDeniedCommand(
174
203
  command: string,
175
204
  ): ToolCallEventResult | undefined {
@@ -1209,7 +1238,10 @@ export default function gentleAi(pi: ExtensionAPI): void {
1209
1238
  return { action: "continue" };
1210
1239
  });
1211
1240
 
1212
- pi.on("before_agent_start", (event, ctx) => {
1241
+ pi.on("before_agent_start", async (event, ctx) => {
1242
+ if (isSddAgentStartEvent(event) && !getSddPreflightPreferences(ctx)) {
1243
+ await runSddPreflight(ctx);
1244
+ }
1213
1245
  const prefs = getSddPreflightPreferences(ctx);
1214
1246
  const sddPrompt = prefs ? `\n\n${renderSddPreflightPrompt(prefs)}` : "";
1215
1247
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gentle-pi",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "Turn Pi into el Gentleman: a senior-architect development harness with SDD/OpenSpec, subagents, strict TDD evidence, review guardrails, and skill discovery.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -162,9 +162,14 @@ async function run() {
162
162
  const promptCwd = await tempWorkspace();
163
163
  try {
164
164
  const promptHook = hooks.get("before_agent_start")[0];
165
- const promptResult = promptHook({ systemPrompt: "base" }, createCtx(promptCwd));
165
+ const promptResult = await promptHook({ systemPrompt: "base" }, createCtx(promptCwd));
166
166
  assert.match(promptResult.systemPrompt, /base/);
167
167
  assert.match(promptResult.systemPrompt, /el Gentleman/);
168
+ assert.equal(
169
+ existsSync(join(promptCwd, ".pi", "agents", "sdd-apply.md")),
170
+ false,
171
+ "normal agent startup must not run SDD preflight",
172
+ );
168
173
  } finally {
169
174
  await rm(promptCwd, { recursive: true, force: true });
170
175
  }
@@ -297,7 +302,7 @@ async function run() {
297
302
  await inputHook({ text: "/sdd-plan another change", source: "interactive" }, ctx);
298
303
  assert.equal(ctx.ui.selections.length, 3, "preflight should run only once per session");
299
304
  const promptHook = hooks.get("before_agent_start")[0];
300
- const promptResult = promptHook({ systemPrompt: "base" }, ctx);
305
+ const promptResult = await promptHook({ systemPrompt: "base" }, ctx);
301
306
  assert.match(promptResult.systemPrompt, /SDD Session Preflight/);
302
307
  assert.match(promptResult.systemPrompt, /Execution mode: interactive/);
303
308
  } finally {
@@ -317,6 +322,34 @@ async function run() {
317
322
  await rm(commandSddCwd, { recursive: true, force: true });
318
323
  }
319
324
 
325
+ const sddAgentGuardCwd = await tempWorkspace();
326
+ try {
327
+ const ctx = createCtx(sddAgentGuardCwd, true, "sdd-agent-guard-session");
328
+ const promptHook = hooks.get("before_agent_start")[0];
329
+ const promptResult = await promptHook(
330
+ {
331
+ systemPrompt: "You are the SDD proposal executor for Gentle AI.",
332
+ },
333
+ ctx,
334
+ );
335
+ assert.equal(existsSync(join(sddAgentGuardCwd, ".pi", "agents", "sdd-apply.md")), true);
336
+ assert.equal(existsSync(join(sddAgentGuardCwd, ".pi", "chains", "sdd-full.chain.md")), true);
337
+ assert.equal(ctx.ui.selections.length, 3);
338
+ assert.match(promptResult.systemPrompt, /SDD Session Preflight/);
339
+ assert.match(ctx.ui.notifications.at(-1).message, /SDD preflight complete/);
340
+
341
+ await promptHook(
342
+ {
343
+ agentName: "sdd-tasks",
344
+ systemPrompt: "You are the SDD tasks executor for Gentle AI.",
345
+ },
346
+ ctx,
347
+ );
348
+ assert.equal(ctx.ui.selections.length, 3, "SDD agent guard should reuse session choices");
349
+ } finally {
350
+ await rm(sddAgentGuardCwd, { recursive: true, force: true });
351
+ }
352
+
320
353
  const invalidPreflightCwd = await tempWorkspace();
321
354
  try {
322
355
  await writeFile(globalModelsPath, "{ invalid json");