gentle-pi 0.3.6 → 0.3.8
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/README.md +7 -2
- package/assets/orchestrator.md +5 -5
- package/package.json +1 -1
- package/skills/release/SKILL.md +108 -0
- package/tests/artifact-language.test.ts +91 -0
- package/tests/runtime-harness.mjs +12 -0
package/README.md
CHANGED
|
@@ -462,12 +462,17 @@ node --experimental-strip-types --check extensions/startup-banner.ts
|
|
|
462
462
|
npm pack --dry-run
|
|
463
463
|
```
|
|
464
464
|
|
|
465
|
-
Publish:
|
|
465
|
+
Publish npm through GitHub Actions only:
|
|
466
466
|
|
|
467
467
|
```bash
|
|
468
|
-
|
|
468
|
+
gh workflow run publish.yml --repo Gentleman-Programming/gentle-pi --ref main -f dist-tag=latest
|
|
469
|
+
gh run watch <run-id> --repo Gentleman-Programming/gentle-pi --exit-status
|
|
470
|
+
npm view gentle-pi@<version> version --registry=https://registry.npmjs.org/
|
|
471
|
+
npm dist-tag ls gentle-pi --registry=https://registry.npmjs.org/
|
|
469
472
|
```
|
|
470
473
|
|
|
474
|
+
Do not run `npm publish` locally for `gentle-pi`; the GitHub workflow provides provenance, environment protection, and registry credentials.
|
|
475
|
+
|
|
471
476
|
## Principles
|
|
472
477
|
|
|
473
478
|
- Human control over agent momentum.
|
package/assets/orchestrator.md
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
3
|
+
"version": "0.3.8",
|
|
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,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: release
|
|
3
|
+
description: "Release gentle-pi through GitHub and npm. Trigger: release, publish, npm publish, GitHub release, version bump."
|
|
4
|
+
license: Apache-2.0
|
|
5
|
+
metadata:
|
|
6
|
+
author: gentleman-programming
|
|
7
|
+
version: "1.0"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## When to Use
|
|
11
|
+
|
|
12
|
+
Use this skill when preparing, publishing, or verifying a `gentle-pi` release.
|
|
13
|
+
|
|
14
|
+
## Hard Rules
|
|
15
|
+
|
|
16
|
+
- Do not publish `gentle-pi` to npm from a local machine.
|
|
17
|
+
- npm publishing MUST go through the GitHub Actions workflow `.github/workflows/publish.yml` so provenance, environment protection, and registry credentials are controlled by GitHub.
|
|
18
|
+
- Use a clean worktree for release commits. Do not package unrelated local files or scratch artifacts.
|
|
19
|
+
- Run a fresh review before pushing a code release unless the change is trivial docs-only.
|
|
20
|
+
- Never skip package verification. The publish workflow runs verification again, but local validation should still pass before tagging.
|
|
21
|
+
|
|
22
|
+
## Release Procedure
|
|
23
|
+
|
|
24
|
+
1. **Inspect state**
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
git status --short
|
|
28
|
+
git fetch origin main --tags
|
|
29
|
+
git log --oneline --decorate --max-count=5 origin/main
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
2. **Prepare the release commit**
|
|
33
|
+
|
|
34
|
+
- Apply only intended changes.
|
|
35
|
+
- Bump `package.json` to the next semver version.
|
|
36
|
+
- Keep lockfile changes out unless dependency resolution actually changed.
|
|
37
|
+
|
|
38
|
+
3. **Verify locally**
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pnpm test
|
|
42
|
+
pnpm publish --dry-run --no-git-checks
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The dry run is allowed because it does not publish. It verifies package contents and lifecycle scripts.
|
|
46
|
+
|
|
47
|
+
4. **Commit and push**
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
git add <intended-files>
|
|
51
|
+
git commit -m "<type(scope): release-ready change>"
|
|
52
|
+
git push origin HEAD:main
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
5. **Create the GitHub release**
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
git tag -a v<version> -m "gentle-pi v<version>"
|
|
59
|
+
git push origin v<version>
|
|
60
|
+
gh release create v<version> \
|
|
61
|
+
--repo Gentleman-Programming/gentle-pi \
|
|
62
|
+
--title "gentle-pi v<version>" \
|
|
63
|
+
--notes "<release notes>"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
6. **Publish npm through GitHub Actions**
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
gh workflow run publish.yml \
|
|
70
|
+
--repo Gentleman-Programming/gentle-pi \
|
|
71
|
+
--ref main \
|
|
72
|
+
-f dist-tag=latest
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Watch the run and fail the release if it fails:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
gh run list --repo Gentleman-Programming/gentle-pi --workflow publish.yml --limit 3
|
|
79
|
+
gh run watch <run-id> --repo Gentleman-Programming/gentle-pi --exit-status
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
7. **Verify npm**
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
npm view gentle-pi@<version> version --registry=https://registry.npmjs.org/
|
|
86
|
+
npm dist-tag ls gentle-pi --registry=https://registry.npmjs.org/
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Failure Handling
|
|
90
|
+
|
|
91
|
+
- If a local `npm publish` fails, do not retry locally. Use the GitHub workflow instead.
|
|
92
|
+
- If the workflow fails, inspect logs with:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
gh run view <run-id> --repo Gentleman-Programming/gentle-pi --log
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
- If npm verification is briefly stale after a successful workflow, check the exact version first (`npm view gentle-pi@<version> version`) before assuming publish failed.
|
|
99
|
+
|
|
100
|
+
## Output Contract
|
|
101
|
+
|
|
102
|
+
Report:
|
|
103
|
+
|
|
104
|
+
- Commit SHA pushed to `main`.
|
|
105
|
+
- GitHub release URL.
|
|
106
|
+
- Publish workflow run URL and conclusion.
|
|
107
|
+
- npm exact version and `latest` dist-tag.
|
|
108
|
+
- Any remaining follow-up or warnings.
|
|
@@ -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),
|