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.
- package/assets/orchestrator.md +8 -1
- package/lib/sdd-preflight.ts +25 -2
- package/package.json +1 -1
- package/tests/runtime-harness.mjs +55 -1
package/assets/orchestrator.md
CHANGED
|
@@ -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
|
|
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
|
|
package/lib/sdd-preflight.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
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(
|
|
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");
|