gentle-pi 0.3.5 → 0.3.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.
@@ -181,6 +181,13 @@ proposal → design ┘
181
181
 
182
182
  Do not ask SDD setup questions on session start. The first time the user initiates an SDD process in a Pi session, run the SDD preflight once and keep those choices for the rest of that session. Runtime trigger detection is intentionally deterministic: slash SDD flows and `/sdd-init` run preflight automatically; for natural-language requests, the parent/orchestrator decides semantically whether SDD is needed and must run/reuse `/gentle-ai:sdd-preflight` before continuing.
183
183
 
184
+ **Hard gate:** `openspec/config.yaml`, existing SDD changes, installed `.pi`/global SDD assets, or a todo named "preflight" are not session preflight. They are project context only. Do not mark SDD preflight complete, start `sdd-init`, launch SDD subagents/chains, or move to explore/proposal/spec/design/tasks until this session has either:
185
+
186
+ 1. an injected `## SDD Session Preflight` block, or
187
+ 2. an explicit user answer in the current conversation covering all four preflight choices below.
188
+
189
+ If neither exists and `/gentle-ai:sdd-preflight` cannot be invoked from the current context, ask the four choices manually with `ask_user_question` before any SDD phase work. Treat missing Engram availability as a reason to ask/confirm artifact store, not as permission to assume defaults.
190
+
184
191
  The preflight captures:
185
192
 
186
193
  - execution mode: `interactive` or `auto`;
@@ -207,7 +214,7 @@ In this Pi package, the default local artifact is:
207
214
  openspec/config.yaml
208
215
  ```
209
216
 
210
- If it is missing, ask the user for the minimal information needed or run `/sdd-init` if available. Do not proceed with a substantial SDD flow while pretending project context and testing capability are known.
217
+ If it is missing, ask the user for the minimal information needed or run `/sdd-init` if available. This init guard runs after the session preflight gate above; project config presence or absence never substitutes for session preflight choices. Do not proceed with a substantial SDD flow while pretending project context, testing capability, or session preflight choices are known.
211
218
 
212
219
  ## Artifact Store Policy
213
220
 
@@ -25,6 +25,7 @@ export interface SddPreflightPreferences {
25
25
  chainedPrStrategy: SddChainedPrStrategy;
26
26
  reviewBudgetLines: number;
27
27
  engramAvailable: boolean;
28
+ prompted: boolean;
28
29
  }
29
30
 
30
31
  interface SddPreflightCallbacks {
@@ -55,6 +56,7 @@ const DEFAULT_SDD_PREFLIGHT: SddPreflightPreferences = {
55
56
  chainedPrStrategy: "auto-forecast",
56
57
  reviewBudgetLines: 400,
57
58
  engramAvailable: false,
59
+ prompted: false,
58
60
  };
59
61
 
60
62
  const sddPreflightBySession = new Map<string, SddPreflightPreferences>();
@@ -122,7 +124,23 @@ export function installSddAssets(
122
124
  }
123
125
 
124
126
  export function isSddPreflightTrigger(text: string): boolean {
125
- return /^\/sdd-[^\s]*(?:\s|$)/i.test(text.trim());
127
+ const trimmed = text.trim();
128
+ if (/^\/sdd(?:[-:][^\s]*)?(?:\s|$)/i.test(trimmed)) return true;
129
+ if (/[??]\s*$/.test(trimmed)) return false;
130
+ if (
131
+ /\b(?:don't|do\s+not|not\s+use|never\s+use|without\s+using|sin\s+usar|no\s+(?:quiero|queremos|vamos\s+a)?\s*usar)\s+sdd\b/i.test(
132
+ trimmed,
133
+ )
134
+ ) {
135
+ return false;
136
+ }
137
+ return [
138
+ /^(?:please\s+)?(?:use|run|start)\s+(?:the\s+|an?\s+)?sdd(?:\s+(?:flow|process|workflow|plan))?\b/i,
139
+ /^(?:please\s+)?(?:do|handle|implement)\b.+\b(?:with|using)\s+(?:the\s+|an?\s+)?sdd\b/i,
140
+ /^(?:por\s+favor[\s,]+)?(?:vamos|vayamos)\s+con\s+(?:el\s+)?sdd\b/i,
141
+ /^(?:por\s+favor[\s,]+)?(?:usa|usá|usemos|corre|corré|arranca|arrancá|inicia|iniciá|empeza|empezá)\s+(?:el\s+)?sdd\b/i,
142
+ /^(?:por\s+favor[\s,]+)?(?:hacelo|hazlo|hacerlo)\s+(?:con|usando)\s+(?:el\s+)?sdd\b/i,
143
+ ].some((pattern) => pattern.test(trimmed));
126
144
  }
127
145
 
128
146
  export function sddPreflightSessionKey(ctx: ExtensionContext): string {
@@ -209,13 +227,17 @@ async function collectSddPreflightPreferences(
209
227
  : DEFAULT_SDD_PREFLIGHT.chainedPrStrategy,
210
228
  reviewBudgetLines,
211
229
  engramAvailable,
230
+ prompted: true,
212
231
  };
213
232
  }
214
233
 
215
234
  export function renderSddPreflightPrompt(prefs: SddPreflightPreferences): string {
235
+ const sourceLine = prefs.prompted
236
+ ? "The user already chose these SDD preferences for this Pi session. Reuse them unless the user explicitly changes them."
237
+ : "No interactive UI was available for SDD preflight, so these default preferences were applied for this Pi session. Ask the user before making delivery decisions that depend on them.";
216
238
  return [
217
239
  "## SDD Session Preflight",
218
- "The user already chose these SDD preferences for this Pi session. Reuse them unless the user explicitly changes them.",
240
+ sourceLine,
219
241
  `- Execution mode: ${prefs.executionMode}`,
220
242
  `- Artifact store: ${prefs.artifactStore}${prefs.engramAvailable ? "" : " (Engram unavailable in this session)"}`,
221
243
  `- Chained PR strategy: ${prefs.chainedPrStrategy}`,
@@ -254,6 +276,7 @@ export async function ensureSddPreflight(
254
276
  `Artifacts: ${prefs.artifactStore}`,
255
277
  `PR chaining: ${prefs.chainedPrStrategy}`,
256
278
  `Review budget: ${prefs.reviewBudgetLines} changed lines`,
279
+ `Preference source: ${prefs.prompted ? "user prompt" : "defaults (no interactive UI available)"}`,
257
280
  `Global SDD assets ready: ${result.agents} agent(s), ${result.chains} chain(s), ${result.support} support file(s), ${result.skipped} already present.`,
258
281
  modelRoutingLine,
259
282
  ].join("\n"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gentle-pi",
3
- "version": "0.3.5",
3
+ "version": "0.3.6",
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",
@@ -177,6 +177,8 @@ async function run() {
177
177
  const promptResult = await promptHook({ systemPrompt: "base" }, createCtx(promptCwd));
178
178
  assert.match(promptResult.systemPrompt, /base/);
179
179
  assert.match(promptResult.systemPrompt, /el Gentleman/);
180
+ assert.match(promptResult.systemPrompt, /openspec\/config\.yaml.*not session preflight/s);
181
+ assert.match(promptResult.systemPrompt, /Do not mark SDD preflight complete/);
180
182
  const subagentPromptResult = await promptHook(
181
183
  { agentName: "worker", systemPrompt: "worker base" },
182
184
  createCtx(promptCwd),
@@ -284,10 +286,26 @@ async function run() {
284
286
  );
285
287
  assert.equal(existsSync(join(lazySddCwd, ".pi", "agents", "sdd-apply.md")), false);
286
288
 
289
+ assert.deepEqual(
290
+ await inputHook({ text: "vamos con sdd", source: "interactive" }, ctx),
291
+ { action: "continue" },
292
+ );
293
+ assert.equal(existsSync(join(lazySddCwd, ".pi", "agents", "sdd-apply.md")), false);
294
+ assert.equal(existsSync(join(lazySddCwd, ".pi", "chains", "sdd-full.chain.md")), false);
295
+ assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-apply.md")), true);
296
+ assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-sync.md")), true);
297
+ assert.equal(existsSync(join(globalAgentHome, "chains", "sdd-full.chain.md")), true);
298
+ assert.equal(ctx.ui.selections.length, 3);
299
+ assert.equal(ctx.ui.selections[0].label, "SDD execution mode");
300
+ assert.equal(ctx.ui.selections[1].label, "SDD artifact store");
301
+ assert.deepEqual(ctx.ui.selections[1].options, ["openspec"]);
302
+ assert.equal(ctx.ui.selections[2].label, "SDD PR chaining");
303
+ assert.match(ctx.ui.notifications.at(-1).message, /Preference source: user prompt/);
287
304
  assert.deepEqual(
288
305
  await inputHook({ text: "please use sdd for this change", source: "interactive" }, ctx),
289
306
  { action: "continue" },
290
307
  );
308
+ assert.equal(ctx.ui.selections.length, 3, "natural SDD trigger should reuse session choices");
291
309
  assert.deepEqual(
292
310
  await inputHook({ text: "/sdd", source: "interactive" }, ctx),
293
311
  { action: "continue" },
@@ -300,7 +318,7 @@ async function run() {
300
318
  await inputHook({ text: "/sdd:plan", source: "interactive" }, ctx),
301
319
  { action: "continue" },
302
320
  );
303
- assert.equal(existsSync(join(lazySddCwd, ".pi", "agents", "sdd-apply.md")), false);
321
+ assert.equal(ctx.ui.selections.length, 3);
304
322
 
305
323
  assert.deepEqual(
306
324
  await inputHook({ text: "/sdd-plan this change", source: "interactive" }, ctx),
@@ -338,6 +356,22 @@ async function run() {
338
356
  await rm(globalModelsPath, { force: true });
339
357
  }
340
358
 
359
+ for (const [index, text] of ["/sdd", "/sdd plan", "/sdd:plan", "/sdd-plan this change"].entries()) {
360
+ const slashSddCwd = await tempWorkspace();
361
+ try {
362
+ const ctx = createCtx(slashSddCwd, true, `slash-sdd-session-${index}`);
363
+ const inputHook = hooks.get("input")[0];
364
+ assert.deepEqual(await inputHook({ text, source: "interactive" }, ctx), {
365
+ action: "continue",
366
+ });
367
+ assert.equal(existsSync(join(slashSddCwd, ".pi", "agents", "sdd-apply.md")), false);
368
+ assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-apply.md")), true);
369
+ assert.equal(ctx.ui.selections.length, 3, `${text} should run canonical preflight`);
370
+ } finally {
371
+ await rm(slashSddCwd, { recursive: true, force: true });
372
+ }
373
+ }
374
+
341
375
  const commandSddCwd = await tempWorkspace();
342
376
  try {
343
377
  const ctx = createCtx(commandSddCwd, true, "command-session");
@@ -396,6 +430,26 @@ async function run() {
396
430
  await rm(sddAgentGuardCwd, { recursive: true, force: true });
397
431
  }
398
432
 
433
+ const noUiSddAgentCwd = await tempWorkspace();
434
+ try {
435
+ const ctx = createCtx(noUiSddAgentCwd, false, "no-ui-sdd-agent-session");
436
+ const promptHook = hooks.get("before_agent_start")[0];
437
+ const promptResult = await promptHook(
438
+ {
439
+ agentName: "sdd-proposal",
440
+ systemPrompt: "You are the SDD proposal executor for Gentle AI.",
441
+ },
442
+ ctx,
443
+ );
444
+ assert.match(promptResult.systemPrompt, /SDD Session Preflight/);
445
+ assert.match(promptResult.systemPrompt, /No interactive UI was available/);
446
+ assert.equal(ctx.ui.selections.length, 0);
447
+ assert.equal(existsSync(join(noUiSddAgentCwd, ".pi", "agents", "sdd-apply.md")), false);
448
+ assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-apply.md")), true);
449
+ } finally {
450
+ await rm(noUiSddAgentCwd, { recursive: true, force: true });
451
+ }
452
+
399
453
  const invalidPreflightCwd = await tempWorkspace();
400
454
  try {
401
455
  await writeFile(globalModelsPath, "{ invalid json");