gentle-pi 3.1.0 → 3.1.1
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-memory.md +1 -1
- package/assets/sdd-orchestrator-workflow.md +1 -1
- package/extensions/gentle-ai.ts +2 -2
- package/extensions/pi-pretty.ts +13 -1
- package/lib/sdd-preflight.ts +5 -19
- package/package.json +1 -1
- package/tests/gentle-theme.test.ts +2 -0
- package/tests/odd-routing-contract.test.ts +5 -0
- package/tests/package-manifest.test.ts +2 -2
- package/tests/pi-pretty.test.ts +5 -0
- package/tests/runtime-harness.mjs +12 -10
- package/tests/sdd-managed-runtime-settlement.test.ts +2 -2
- package/tests/sdd-preflight-rpc-input.test.ts +29 -29
- package/tests/sdd-preflight.test.ts +12 -7
- package/tests/windows-hidden-processes.test.ts +2 -2
- package/themes/Gentleman-Cute.json +1 -1
|
@@ -14,7 +14,7 @@ On resume, use `mem_context`, then project/feature-scoped `mem_search`, and `mem
|
|
|
14
14
|
|
|
15
15
|
Before implementation or resume, the parent reads both the actual file and full observation, reconciles them, and passes the locator and relevant context; workers read the document before edits. Small work without a document still receives its authorized scope and checks.
|
|
16
16
|
|
|
17
|
-
The existing `todo` tool is
|
|
17
|
+
The existing `todo` tool is the required session/UI projection for substantial ODD, not a third authority. After reconciling and writing the durable file and Engram copy, create or rebuild the visible `todo` list from the same feature tasks before the first source write; after every task transition and material plan change, update both durable copies and the visible projection in the same turn; its replay or completed-list clearing must not delete or replace the durable file or Engram copy. If the projection is unavailable, record that limitation without pretending it is synchronized. Small/read-only work does not acquire an ODD artifact or todo list merely because the UI can display tasks.
|
|
18
18
|
|
|
19
19
|
### SDD phases
|
|
20
20
|
|
|
@@ -71,7 +71,7 @@ Do not guess the active change. If change selection is ambiguous, ask the user a
|
|
|
71
71
|
|
|
72
72
|
## Lazy SDD Preflight
|
|
73
73
|
|
|
74
|
-
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
|
|
74
|
+
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 input detection is intentionally syntax-only: slash SDD flows and `/gentle-sdd-init` run preflight automatically; ordinary natural-language text never triggers preflight by itself. For natural-language requests, the parent/orchestrator decides semantically whether the user explicitly selected SDD. Only after that selection, when the parent is about to dispatch the first SDD operation or agent, the execution gate runs or reuses `/gentle:sdd-preflight` before allowing SDD work to start. Merely mentioning, asking about, explaining, comparing, auditing, or referencing SDD — including questions ("what is SDD?"), negatives ("don't use SDD for this"), conditionals ("could we use SDD?"), bug reports about SDD itself, and passing references — stays in the normal read-only flow without preflight, the init guard, or any SDD phase. When a message is ambiguous between discussing SDD and requesting it, ask one clarification and remain read-only until answered.
|
|
75
75
|
|
|
76
76
|
**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 an injected `## SDD Session Preflight` block or an equivalent resolution from the canonical authority order below.
|
|
77
77
|
|
package/extensions/gentle-ai.ts
CHANGED
|
@@ -1388,8 +1388,8 @@ Organic Driven Development (ODD) is the predefined workflow of this orchestrator
|
|
|
1388
1388
|
2. **Explore.** Explore existing code and requirements first, proportionately to the request, before proposing or writing anything.
|
|
1389
1389
|
3. **Resolve uncertainty.** Recommend optional research only for a named uncertainty; ask one focused user question only for a real unresolved product decision, then stop and wait; use at most one scoped read-only assumption challenge for a high-consequence unproven premise.
|
|
1390
1390
|
4. **Classify.** The work is substantial when exploration yields two or more meaningful implementation steps, or progress worth recovering after an interruption. Small, understood work stays small and creates no durable task artifacts.
|
|
1391
|
-
5. **Track before the first write.** For substantial authorized implementation, create \`odd/tasks/<feature-name>.md\` and its Engram mirror \`odd/<feature-name>/tasks\` automatically, before the first source write
|
|
1392
|
-
6. **Implement task by task.** Route each task through the orchestrator's Work Routing Ladder, with the configured TDD mode and applicable checks. Check an item off only after its outcome and checks were observed; update the file and
|
|
1391
|
+
5. **Track before the first write.** For substantial authorized implementation, create \`odd/tasks/<feature-name>.md\` and its Engram mirror \`odd/<feature-name>/tasks\` automatically, then create or rebuild the visible \`todo\` list from the reconciled feature tasks, all before the first source write and without asking permission for tasks or storage. Tell the user in one line which feature document was created and how many tasks it holds.
|
|
1392
|
+
6. **Implement task by task.** Route each task through the orchestrator's Work Routing Ladder, with the configured TDD mode and applicable checks. Check an item off only after its outcome and checks were observed; update the file, mirror, and visible \`todo\` projection after every task transition and material plan change.
|
|
1393
1393
|
7. **Close.** Report the verified outcome, every failed, skipped, or pending check, and the next step. Native review applies only at the deliverable boundary and only under the user-owned RDD switch.
|
|
1394
1394
|
Resume an interrupted feature with \`mem_context\`, then project- and feature-scoped \`mem_search\`, then \`mem_get_observation\` for the full document, then the task file itself; reconcile before continuing the next unfinished task. Detail for steps 3–7: \`orchestrator-delegation.md\` and \`orchestrator-memory.md\`.
|
|
1395
1395
|
|
package/extensions/pi-pretty.ts
CHANGED
|
@@ -61,7 +61,19 @@ export default async function gentlePiPrettyExtension(
|
|
|
61
61
|
};
|
|
62
62
|
},
|
|
63
63
|
});
|
|
64
|
-
|
|
64
|
+
// pi-pretty's hidden-thinking API is global. Its best-effort private per-row
|
|
65
|
+
// patch can miss the host's actual component class and then animate every
|
|
66
|
+
// historical Thinking row. Gentle Shell chooses the robust presentation:
|
|
67
|
+
// keep collapsed labels static while preserving the separate working frame.
|
|
68
|
+
const previousThinkingIndicator = process.env.PRETTY_THINKING_INDICATOR;
|
|
69
|
+
process.env.PRETTY_THINKING_INDICATOR = "off";
|
|
70
|
+
let result: unknown;
|
|
71
|
+
try {
|
|
72
|
+
result = await extension(delegated, deps);
|
|
73
|
+
} finally {
|
|
74
|
+
if (previousThinkingIndicator === undefined) delete process.env.PRETTY_THINKING_INDICATOR;
|
|
75
|
+
else process.env.PRETTY_THINKING_INDICATOR = previousThinkingIndicator;
|
|
76
|
+
}
|
|
65
77
|
// GentlePromptEditor already carries the live working state in its frame.
|
|
66
78
|
// Hide Pi's separate loader row to avoid repeating Thinking above the input;
|
|
67
79
|
// transcript thinking blocks remain untouched as historical reasoning markers.
|
package/lib/sdd-preflight.ts
CHANGED
|
@@ -835,26 +835,12 @@ export function installPackageAssets(
|
|
|
835
835
|
}, lockOptions);
|
|
836
836
|
}
|
|
837
837
|
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
// "I use SDD sometimes" remains ordinary conversation.
|
|
843
|
-
if (!/\bsdd\b/i.test(text)) return false;
|
|
844
|
-
return /(?:\b(?:please|por\s+favor)\b|\b(?:want|need|would\s+like|let'?s|quiero|queremos|necesito|quisiera|me\s+gustar[ií]a|vamos|vayamos|hagamos|usemos)\b|^(?:use|run|start|build|create|implement|handle|make|usa|usá|corre|corré|arranca|arrancá|inicia|iniciá|empeza|empezá|hacelo|hazlo|hacerlo)\b)/i.test(text);
|
|
845
|
-
}
|
|
846
|
-
|
|
838
|
+
// Input interception is intentionally syntax-only. Natural-language SDD intent
|
|
839
|
+
// belongs to the parent/orchestrator; dispatch and before_agent_start gates run
|
|
840
|
+
// or reuse preflight when an SDD action is actually attempted. This keeps mere
|
|
841
|
+
// mentions side-effect free without trying to encode language in a regex.
|
|
847
842
|
export function isSddPreflightTrigger(text: string): boolean {
|
|
848
|
-
|
|
849
|
-
if (/^\/(?:gentle-)?sdd(?:[-:][^\s]*)?(?:\s|$)/i.test(trimmed)) return true;
|
|
850
|
-
if (/[??]\s*$/.test(trimmed)) return false;
|
|
851
|
-
if (
|
|
852
|
-
/(?:\b(?:don't|do\s+not|never)\b|\bnot\s+(?:want|need|plan(?:ning)?|intend|use|using)\b)[^.!?\n]{0,80}\bsdd\b/i.test(trimmed) ||
|
|
853
|
-
/\b(?:sin\s+usar|no\s+(?:quiero|queremos|necesito|necesitamos|quisiera|quisiéramos|vamos\s+a|pienso|planeo|usar))\b[^.!?\n]{0,80}\bsdd\b/i.test(trimmed)
|
|
854
|
-
) {
|
|
855
|
-
return false;
|
|
856
|
-
}
|
|
857
|
-
return hasAffirmativeSddIntent(trimmed);
|
|
843
|
+
return /^\/(?:gentle-)?sdd(?:[-:][^\s]*)?(?:\s|$)/i.test(text.trim());
|
|
858
844
|
}
|
|
859
845
|
|
|
860
846
|
export function sddPreflightSessionKey(ctx: ExtensionContext): string {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gentle-pi",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.1",
|
|
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",
|
|
@@ -219,6 +219,8 @@ test("bundled Gentleman-Cute Pi theme defines complete brand and restrained colo
|
|
|
219
219
|
assertResolvedThemeColor(theme, "error", "#FF718F");
|
|
220
220
|
assertResolvedThemeColor(theme, "muted", "#A78E9B");
|
|
221
221
|
assertResolvedThemeColor(theme, "dim", "#76616B");
|
|
222
|
+
assert.equal(colors.thinkingText, "softRose");
|
|
223
|
+
assertResolvedThemeColor(theme, "thinkingText", "#D7A0B8");
|
|
222
224
|
assertResolvedThemeColor(theme, "toolPendingBg", "#100A0F");
|
|
223
225
|
assertResolvedThemeColor(theme, "toolSuccessBg", "#151316");
|
|
224
226
|
assertResolvedThemeColor(theme, "toolErrorBg", "#261019");
|
|
@@ -81,7 +81,11 @@ test("organic progress preserves both complete feature copies and reconciles act
|
|
|
81
81
|
"mem_get_observation",
|
|
82
82
|
"read the actual task file",
|
|
83
83
|
"not a third authority",
|
|
84
|
+
"create or rebuild the visible `todo` list",
|
|
85
|
+
"before the first source write",
|
|
86
|
+
"after every task transition and material plan change",
|
|
84
87
|
]);
|
|
88
|
+
assert.doesNotMatch(memory, /`todo` tool is an optional session\/UI projection/);
|
|
85
89
|
});
|
|
86
90
|
|
|
87
91
|
test("assumption challenge and task checks do not activate or duplicate native review", () => {
|
|
@@ -170,6 +174,7 @@ test("ODD protocol is always-on in the rendered system prompt and runs by defaul
|
|
|
170
174
|
"4. **Classify.**",
|
|
171
175
|
"two or more meaningful implementation steps",
|
|
172
176
|
"5. **Track before the first write.**",
|
|
177
|
+
"create or rebuild the visible `todo` list from the reconciled feature tasks",
|
|
173
178
|
"Tell the user in one line which feature document was created and how many tasks it holds",
|
|
174
179
|
"6. **Implement task by task.**",
|
|
175
180
|
"7. **Close.**",
|
|
@@ -1574,9 +1574,9 @@ test("pi-pretty wrapper uses cached ESM loading for compiled and pnpm symlink in
|
|
|
1574
1574
|
assert.match(wrapper, /quietToolsEnabled/);
|
|
1575
1575
|
});
|
|
1576
1576
|
|
|
1577
|
-
test("v3.1.
|
|
1577
|
+
test("Gentle Shell v3.1.1 package and runtime stop before publication", () => {
|
|
1578
1578
|
const packageJson = readPackageJson();
|
|
1579
|
-
assert.equal(packageJson.version, "3.1.
|
|
1579
|
+
assert.equal(packageJson.version, "3.1.1", "the release manifest must remain explicitly pinned to v3.1.1");
|
|
1580
1580
|
assert.equal(
|
|
1581
1581
|
packageJson.scripts?.test,
|
|
1582
1582
|
"node --experimental-strip-types --test tests/*.test.ts && pnpm run check:provider-contract && pnpm run test:harness",
|
package/tests/pi-pretty.test.ts
CHANGED
|
@@ -18,13 +18,18 @@ test("bundled pretty cannot replace or restore an editor owned by the host", asy
|
|
|
18
18
|
setWorkingVisible(value: boolean) { visible = value; },
|
|
19
19
|
theme: { fg: (role: string, text: string) => `<${role}>${text}</${role}>`, bold: (text: string) => text },
|
|
20
20
|
} };
|
|
21
|
+
const previousThinkingIndicator = process.env.PRETTY_THINKING_INDICATOR;
|
|
22
|
+
let thinkingIndicatorDuringActivation: string | undefined;
|
|
21
23
|
await pretty(pi, undefined, async (api: any) => {
|
|
24
|
+
thinkingIndicatorDuringActivation = process.env.PRETTY_THINKING_INDICATOR;
|
|
22
25
|
api.on("session_start", (_event: unknown, context: any) => {
|
|
23
26
|
context.ui.setEditorComponent(() => "pretty");
|
|
24
27
|
context.ui.setWorkingIndicator({ frames: ["foreign"] });
|
|
25
28
|
});
|
|
26
29
|
api.on("session_shutdown", (_event: unknown, context: any) => context.ui.setEditorComponent(undefined));
|
|
27
30
|
}, {});
|
|
31
|
+
assert.equal(thinkingIndicatorDuringActivation, "off", "Gentle Shell disables pi-pretty's global Thinking shimmer");
|
|
32
|
+
assert.equal(process.env.PRETTY_THINKING_INDICATOR, previousThinkingIndicator, "activation must restore the caller's environment");
|
|
28
33
|
for (const handler of handlers.get("session_start") ?? []) await handler({}, ctx);
|
|
29
34
|
assert.equal(editor, owner);
|
|
30
35
|
assert.equal(visible, false, "the prompt frame owns the live working state without a duplicate loader row");
|
|
@@ -1250,24 +1250,26 @@ async function run() {
|
|
|
1250
1250
|
await inputHook({ text: "vamos con sdd", source: "interactive" }, ctx),
|
|
1251
1251
|
{ action: "continue" },
|
|
1252
1252
|
);
|
|
1253
|
-
assert.equal(existsSync(join(lazySddCwd, ".pi", "agents", "sdd-apply.md")), false);
|
|
1254
|
-
assert.equal(existsSync(join(lazySddCwd, ".pi", "chains", "sdd-full.chain.md")), false);
|
|
1255
|
-
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-apply.md")), true);
|
|
1256
|
-
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-status.md")), true);
|
|
1257
|
-
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-sync.md")), false);
|
|
1258
|
-
assert.equal(existsSync(join(globalAgentHome, "gentle-ai", "support", "sdd-status-contract.md")), true);
|
|
1259
|
-
assert.equal(existsSync(join(globalAgentHome, "chains", "sdd-full.chain.md")), true);
|
|
1260
|
-
assert.equal(ctx.ui.selections.length, 1, "first interactive SDD trigger confirms session suggestions");
|
|
1261
|
-
assert.match(ctx.ui.notifications.at(-1).message, /Preference source: explicit session choice/);
|
|
1262
1253
|
assert.deepEqual(
|
|
1263
1254
|
await inputHook({ text: "please use sdd for this change", source: "interactive" }, ctx),
|
|
1264
1255
|
{ action: "continue" },
|
|
1265
1256
|
);
|
|
1266
|
-
assert.equal(
|
|
1257
|
+
assert.equal(existsSync(join(lazySddCwd, ".pi", "agents", "sdd-apply.md")), false);
|
|
1258
|
+
assert.equal(existsSync(join(lazySddCwd, ".pi", "chains", "sdd-full.chain.md")), false);
|
|
1259
|
+
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-apply.md")), false);
|
|
1260
|
+
assert.equal(ctx.ui.selections.length, 0, "natural-language SDD text has no input-hook side effect");
|
|
1261
|
+
|
|
1267
1262
|
assert.deepEqual(
|
|
1268
1263
|
await inputHook({ text: "/sdd", source: "interactive" }, ctx),
|
|
1269
1264
|
{ action: "continue" },
|
|
1270
1265
|
);
|
|
1266
|
+
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-apply.md")), true);
|
|
1267
|
+
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-status.md")), true);
|
|
1268
|
+
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-sync.md")), false);
|
|
1269
|
+
assert.equal(existsSync(join(globalAgentHome, "gentle-ai", "support", "sdd-status-contract.md")), true);
|
|
1270
|
+
assert.equal(existsSync(join(globalAgentHome, "chains", "sdd-full.chain.md")), true);
|
|
1271
|
+
assert.equal(ctx.ui.selections.length, 1, "first slash SDD trigger confirms session suggestions");
|
|
1272
|
+
assert.match(ctx.ui.notifications.at(-1).message, /Preference source: explicit session choice/);
|
|
1271
1273
|
assert.deepEqual(
|
|
1272
1274
|
await inputHook({ text: "/sdd plan", source: "interactive" }, ctx),
|
|
1273
1275
|
{ action: "continue" },
|
|
@@ -75,9 +75,9 @@ test("remediation actor preserves separately authorized memory artifact tools",
|
|
|
75
75
|
|
|
76
76
|
test("R1 confirms exact canonical paths and commands; data, denial and symlinks grant nothing", async t => {
|
|
77
77
|
const { confirmRemediationScope, remediationToolAllowed } = await import("../extensions/gentle-agents.ts");
|
|
78
|
-
const { mkdtempSync, writeFileSync, symlinkSync, rmSync } = await import("node:fs");
|
|
78
|
+
const { mkdtempSync, realpathSync, writeFileSync, symlinkSync, rmSync } = await import("node:fs");
|
|
79
79
|
const { tmpdir } = await import("node:os"); const { join } = await import("node:path");
|
|
80
|
-
const cwd = mkdtempSync(join(tmpdir(), "remediation-scope-")); t.after(() => rmSync(cwd, { recursive: true, force: true }));
|
|
80
|
+
const cwd = realpathSync(mkdtempSync(join(tmpdir(), "remediation-scope-"))); t.after(() => rmSync(cwd, { recursive: true, force: true }));
|
|
81
81
|
const target = join(cwd, "allowed.ts"); writeFileSync(target, "original"); symlinkSync(target, join(cwd, "alias.ts"));
|
|
82
82
|
const candidate = { ...plan, cwd, editPaths: [target] }, native = { mode: "repo-local", workspaceRoot: cwd, allowedEditRoots: [cwd] };
|
|
83
83
|
let shown = "", confirmations = 0;
|
|
@@ -8,17 +8,12 @@ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-a
|
|
|
8
8
|
import { createGentleAiExtension } from "../extensions/gentle-ai.ts";
|
|
9
9
|
import { isSddPreflightTrigger, sddPreflightDiskPath } from "../lib/sdd-preflight.ts";
|
|
10
10
|
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
// calls, and a stall that only surfaced as the runner's inactivity timeout.
|
|
18
|
-
//
|
|
19
|
-
// An RPC child consumes the parent-rendered preflight block transported in its
|
|
20
|
-
// task context. It must never originate preflight, and the interceptor must
|
|
21
|
-
// never consume a delegated prompt on its behalf.
|
|
11
|
+
// The `input` hook is syntax-only: slash SDD commands may originate preflight
|
|
12
|
+
// in an interactive parent, while ordinary natural-language text always reaches
|
|
13
|
+
// the model. Natural-language SDD selection belongs to the parent/orchestrator;
|
|
14
|
+
// dispatch and before_agent_start gates enforce preflight at the action boundary.
|
|
15
|
+
// RPC children consume the parent-rendered preflight block transported in task
|
|
16
|
+
// context and never originate preflight.
|
|
22
17
|
|
|
23
18
|
type InputResult = { action: "continue" | "handled" };
|
|
24
19
|
type InputHandler = (event: { text?: unknown }, ctx: ExtensionContext) => Promise<InputResult>;
|
|
@@ -52,13 +47,11 @@ function ctx(overrides: Record<string, unknown>): ExtensionContext {
|
|
|
52
47
|
} as unknown as ExtensionContext;
|
|
53
48
|
}
|
|
54
49
|
|
|
55
|
-
test("
|
|
56
|
-
|
|
57
|
-
// this text they would pass for the wrong reason.
|
|
58
|
-
assert.equal(isSddPreflightTrigger(DELEGATED_SDD_TASK), true);
|
|
50
|
+
test("natural-language SDD task text bypasses the input preflight interceptor", () => {
|
|
51
|
+
assert.equal(isSddPreflightTrigger(DELEGATED_SDD_TASK), false);
|
|
59
52
|
});
|
|
60
53
|
|
|
61
|
-
test("an RPC child's
|
|
54
|
+
test("an RPC child's natural-language SDD prompt is not consumed by the input hook", async () => {
|
|
62
55
|
const input = inputHook();
|
|
63
56
|
const cwd = await mkdtemp(join(tmpdir(), "gentle-pi-rpc-input-"));
|
|
64
57
|
const notifications: string[] = [];
|
|
@@ -95,27 +88,34 @@ test("an RPC child's ordinary prompt still continues", async () => {
|
|
|
95
88
|
}
|
|
96
89
|
});
|
|
97
90
|
|
|
98
|
-
test("an interactive parent
|
|
91
|
+
test("an interactive parent's natural-language SDD request has no input-hook side effect", async () => {
|
|
99
92
|
const input = inputHook();
|
|
100
|
-
const cwd = await mkdtemp(join(tmpdir(), "gentle-pi-parent-input-"));
|
|
93
|
+
const cwd = await mkdtemp(join(tmpdir(), "gentle-pi-parent-natural-input-"));
|
|
94
|
+
try {
|
|
95
|
+
const result = await input(
|
|
96
|
+
{ text: DELEGATED_SDD_TASK },
|
|
97
|
+
ctx({ cwd, hasUI: false, sessionManager: { getSessionId: () => "sdd-preflight-natural-parent" } }),
|
|
98
|
+
);
|
|
99
|
+
assert.deepEqual(result, { action: "continue" });
|
|
100
|
+
assert.equal(existsSync(sddPreflightDiskPath(cwd)), false, "text alone must not persist preflight");
|
|
101
|
+
} finally {
|
|
102
|
+
await rm(cwd, { recursive: true, force: true });
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("an interactive parent still resolves preflight for an explicit slash SDD command", async () => {
|
|
107
|
+
const input = inputHook();
|
|
108
|
+
const cwd = await mkdtemp(join(tmpdir(), "gentle-pi-parent-slash-input-"));
|
|
101
109
|
const agentHome = await mkdtemp(join(tmpdir(), "gentle-pi-parent-agent-home-"));
|
|
102
110
|
const previousAgentHome = process.env.GENTLE_PI_AGENT_HOME;
|
|
103
111
|
process.env.GENTLE_PI_AGENT_HOME = agentHome;
|
|
104
112
|
try {
|
|
105
113
|
const result = await input(
|
|
106
|
-
{ text:
|
|
107
|
-
ctx({
|
|
108
|
-
cwd,
|
|
109
|
-
hasUI: false,
|
|
110
|
-
sessionManager: { getSessionId: () => "sdd-preflight-interactive-parent" },
|
|
111
|
-
}),
|
|
114
|
+
{ text: "/sdd-new feature" },
|
|
115
|
+
ctx({ cwd, hasUI: false, sessionManager: { getSessionId: () => "sdd-preflight-slash-parent" } }),
|
|
112
116
|
);
|
|
113
117
|
assert.deepEqual(result, { action: "continue" });
|
|
114
|
-
assert.equal(
|
|
115
|
-
existsSync(sddPreflightDiskPath(cwd)),
|
|
116
|
-
true,
|
|
117
|
-
"the parent remains the only actor that resolves and persists preflight",
|
|
118
|
-
);
|
|
118
|
+
assert.equal(existsSync(sddPreflightDiskPath(cwd)), true, "slash SDD commands still resolve preflight");
|
|
119
119
|
} finally {
|
|
120
120
|
if (previousAgentHome === undefined) delete process.env.GENTLE_PI_AGENT_HOME;
|
|
121
121
|
else process.env.GENTLE_PI_AGENT_HOME = previousAgentHome;
|
|
@@ -507,20 +507,25 @@ test("only a structurally valid parent-rendered preflight block can reach an SDD
|
|
|
507
507
|
assert.equal(isParentConfirmedSddPreflightContext("## SDD Session Preflight\ncaller-authored defaults"), false);
|
|
508
508
|
});
|
|
509
509
|
|
|
510
|
-
test("
|
|
510
|
+
test("natural-language SDD text never triggers input preflight", () => {
|
|
511
|
+
// Natural-language intent belongs to the parent/orchestrator. The input hook
|
|
512
|
+
// is syntax-only; execution gates enforce preflight when an SDD action is
|
|
513
|
+
// actually attempted.
|
|
511
514
|
for (const text of [
|
|
512
515
|
"quiero hacer un proyecto con SDD",
|
|
513
516
|
"I want to build this with SDD",
|
|
514
517
|
"por favor usemos SDD para este cambio",
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
for (const text of [
|
|
518
|
+
"do SDD for this change",
|
|
519
|
+
"haz SDD para este cambio",
|
|
520
|
+
"continue the SDD change",
|
|
519
521
|
"Should we use SDD?",
|
|
520
522
|
"no quiero usar SDD por ahora",
|
|
521
|
-
"no necesito usar SDD",
|
|
522
|
-
"I don't want to use SDD",
|
|
523
523
|
"I use SDD sometimes",
|
|
524
|
+
"necesito reportar un bug del preflight de SDD",
|
|
525
|
+
"I need to report a bug in the SDD preflight",
|
|
526
|
+
"quiero comparar SDD con ODD",
|
|
527
|
+
"I want to review the SDD proposal",
|
|
528
|
+
"necesito una explicación de SDD",
|
|
524
529
|
]) {
|
|
525
530
|
assert.equal(isSddPreflightTrigger(text), false, text);
|
|
526
531
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { createRequire, syncBuiltinESMExports } from "node:module";
|
|
3
|
-
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { promisify } from "node:util";
|
|
@@ -102,7 +102,7 @@ async function captureOwnedRoutes(t: TestContext): Promise<{ captures: Capture[]
|
|
|
102
102
|
const cp = childProcess();
|
|
103
103
|
const original = { execFileSync: cp.execFileSync, execFile: cp.execFile, spawnSync: cp.spawnSync };
|
|
104
104
|
const captures: Capture[] = [];
|
|
105
|
-
const cwd = mkdtempSync(join(tmpdir(), "gentle-pi-windows-hidden-workspace-"));
|
|
105
|
+
const cwd = realpathSync(mkdtempSync(join(tmpdir(), "gentle-pi-windows-hidden-workspace-")));
|
|
106
106
|
t.after(() => rmSync(cwd, { recursive: true, force: true }));
|
|
107
107
|
mkdirSync(join(cwd, ".git"), { recursive: true });
|
|
108
108
|
const fakeSync = ((command: string, args: readonly string[], options: Options = {}) => {
|