gentle-pi 0.3.6 → 0.3.7

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.
@@ -6,16 +6,16 @@ Bind this to the parent Pi session only. Do not apply it to SDD executor phase a
6
6
 
7
7
  You are el Gentleman: a Pi-specific coding-agent harness for controlled development work.
8
8
 
9
- When the user asks who or what you are, answer in this shape:
9
+ When the user asks who or what you are, answer with this meaning, translated into the user's language:
10
10
 
11
11
  ```text
12
- Soy el Gentleman: un harness específico de Pi para desarrollo controlado, con persona de arquitecto senior. Trabajo con SDD/OpenSpec cuando la tarea lo justifica, coordino subagentes, uso artifacts de fase, corro comandos y edito archivos. No soy un chatbot genérico.
12
+ I am el Gentleman: a Pi-specific coding-agent harness for controlled development, with a senior architect persona. I work with SDD/OpenSpec when the task justifies it, coordinate subagents, use phase artifacts, run commands, and edit files. I am not a generic chatbot.
13
13
  ```
14
14
 
15
15
  Rules:
16
16
 
17
17
  - Never introduce yourself as only "your assistant" or "the default assistant".
18
- - Keep the response in the user's language; in Spanish, use natural Rioplatense voseo.
18
+ - Keep the response in the user's language and follow the currently selected persona mode.
19
19
  - Mention persistent memory only when a memory package or callable memory tools are actually active.
20
20
  - Do not claim portability outside the Pi runtime.
21
21
 
@@ -45,7 +45,7 @@ el Gentleman is an ecosystem configurator and harness layer. After installation,
45
45
 
46
46
  - Small request: do it directly.
47
47
  - Substantial feature: suggest SDD organically.
48
- - User says "use sdd" / "hacelo con sdd": run the SDD flow.
48
+ - User explicitly asks to use SDD: run the SDD flow.
49
49
  - Parent session orchestrates; phase agents execute.
50
50
 
51
51
  Delegation is not optional once complexity appears. If a task crosses the triggers below, use the smallest useful subagent workflow instead of continuing as a monolithic executor.
@@ -101,7 +101,7 @@ Triggers:
101
101
  - cross-cutting behavior changes;
102
102
  - expected large diff or reviewer burden;
103
103
  - need for specs/design/tasks before safe implementation;
104
- - user explicitly says `use sdd`, `hacelo con sdd`, `/sdd-new`, `/sdd-ff`, or `/sdd-continue`.
104
+ - user explicitly asks to use SDD, or invokes `/sdd-new`, `/sdd-ff`, or `/sdd-continue`.
105
105
 
106
106
  If the request is large enough for SDD, do not jump directly to implementation. Calibrate context, create artifacts, and ask for approval at the appropriate gates.
107
107
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gentle-pi",
3
- "version": "0.3.6",
3
+ "version": "0.3.7",
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",
@@ -0,0 +1,91 @@
1
+ import assert from "node:assert/strict";
2
+ import { readdir, readFile } from "node:fs/promises";
3
+ import { dirname, extname, join, relative } from "node:path";
4
+ import test from "node:test";
5
+ import { fileURLToPath } from "node:url";
6
+ import {
7
+ renderSddPreflightPrompt,
8
+ type SddPreflightPreferences,
9
+ } from "../lib/sdd-preflight.ts";
10
+
11
+ const ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
12
+ const TEXT_EXTENSIONS = new Set([".md", ".ts", ".mjs", ".json"]);
13
+
14
+ async function collectTextFiles(dir: string): Promise<string[]> {
15
+ const entries = await readdir(dir, { withFileTypes: true });
16
+ const files: string[] = [];
17
+ for (const entry of entries) {
18
+ const path = join(dir, entry.name);
19
+ if (entry.isDirectory()) {
20
+ files.push(...(await collectTextFiles(path)));
21
+ continue;
22
+ }
23
+ if (entry.isFile() && TEXT_EXTENSIONS.has(extname(entry.name))) {
24
+ files.push(path);
25
+ }
26
+ }
27
+ return files;
28
+ }
29
+
30
+ const SPANISH_PREFLIGHT_COPY = [
31
+ /Antes de continuar con SDD/i,
32
+ /Antes de seguir con SDD/i,
33
+ /una opci[oó]n por grupo/i,
34
+ /usar recomendad[oa]/i,
35
+ /\bRitmo\b/i,
36
+ /\bArtefactos\b/i,
37
+ /\bPreguntarme\b/i,
38
+ /l[ií]neas cambiadas/i,
39
+ /\bhacelo\b/i,
40
+ /\bSoy el Gentleman\b/i,
41
+ ];
42
+
43
+ test("orchestrator keeps conversation language separate from generated artifact language", async () => {
44
+ const orchestrator = await readFile(join(ROOT, "assets/orchestrator.md"), "utf8");
45
+
46
+ assert.match(
47
+ orchestrator,
48
+ /User-facing conversation should stay in the user's language/,
49
+ );
50
+ assert.match(
51
+ orchestrator,
52
+ /Generated artifacts[\s\S]*default to English, regardless of the user's conversation language/,
53
+ );
54
+ });
55
+
56
+ test("rendered SDD preflight prompt is English artifact copy", () => {
57
+ const prefs: SddPreflightPreferences = {
58
+ executionMode: "interactive",
59
+ artifactStore: "openspec",
60
+ chainedPrStrategy: "ask-always",
61
+ reviewBudgetLines: 400,
62
+ engramAvailable: false,
63
+ prompted: true,
64
+ };
65
+ const prompt = renderSddPreflightPrompt(prefs);
66
+
67
+ assert.match(prompt, /The user already chose these SDD preferences/);
68
+ assert.match(prompt, /Review budget: 400 changed lines/);
69
+ for (const pattern of SPANISH_PREFLIGHT_COPY) {
70
+ assert.doesNotMatch(prompt, pattern);
71
+ }
72
+ });
73
+
74
+ test("persistent harness prompt assets do not hardcode Spanish SDD artifact copy", async () => {
75
+ const files = [
76
+ ...(await collectTextFiles(join(ROOT, "assets"))),
77
+ ...(await collectTextFiles(join(ROOT, "prompts"))),
78
+ ];
79
+ const failures: string[] = [];
80
+
81
+ for (const file of files) {
82
+ const text = await readFile(file, "utf8");
83
+ for (const pattern of SPANISH_PREFLIGHT_COPY) {
84
+ if (pattern.test(text)) {
85
+ failures.push(`${relative(ROOT, file)} matched ${pattern}`);
86
+ }
87
+ }
88
+ }
89
+
90
+ assert.deepEqual(failures, []);
91
+ });
@@ -179,6 +179,18 @@ async function run() {
179
179
  assert.match(promptResult.systemPrompt, /el Gentleman/);
180
180
  assert.match(promptResult.systemPrompt, /openspec\/config\.yaml.*not session preflight/s);
181
181
  assert.match(promptResult.systemPrompt, /Do not mark SDD preflight complete/);
182
+ await mkdir(join(promptCwd, ".pi", "gentle-ai"), { recursive: true });
183
+ await writeFile(
184
+ join(promptCwd, ".pi", "gentle-ai", "persona.json"),
185
+ '{"mode":"neutral"}\n',
186
+ );
187
+ const neutralPromptResult = await promptHook({ systemPrompt: "base" }, createCtx(promptCwd));
188
+ assert.match(neutralPromptResult.systemPrompt, /Do not use slang or regional expressions/);
189
+ assert.doesNotMatch(
190
+ neutralPromptResult.systemPrompt,
191
+ /When the user writes Spanish, answer in natural Rioplatense Spanish with voseo/,
192
+ "neutral persona prompt must not include unconditional voseo instructions after reload",
193
+ );
182
194
  const subagentPromptResult = await promptHook(
183
195
  { agentName: "worker", systemPrompt: "worker base" },
184
196
  createCtx(promptCwd),