gentle-pi 4.2.0 → 4.3.0
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 +336 -137
- package/assets/agents/gentle-ai-explore.md +1 -0
- package/assets/agents/gentle-ai-verify.md +6 -4
- package/assets/agents/gentle-ai-worker.md +7 -1
- package/assets/orchestrator-delegation-reference.md +163 -0
- package/assets/orchestrator-delegation.md +25 -176
- package/assets/orchestrator-tracking.md +1 -1
- package/assets/orchestrator-verification.md +14 -8
- package/assets/orchestrator-writer.md +5 -1
- package/assets/orchestrator.md +9 -9
- package/bin/gentle-shell.mjs +170 -22
- package/contracts/review-provider-contract-mirror/provider-contract.lock.json +8 -7
- package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/README.md +7 -0
- package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/manifest.json +11 -2
- package/contracts/review-provider-contract-mirror/v1.4.0/bundle/routing/pi.md +92 -0
- package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/generated/provider-capabilities.baseline.json +8 -1
- package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/generated/provider-roles.baseline.json +1 -1
- package/docs/assets/diagrams/agent-orchestration.svg +54 -15
- package/docs/assets/diagrams/gentleman-workflow.svg +69 -11
- package/docs/assets/diagrams/measured-routing.svg +49 -0
- package/docs/assets/diagrams/native-review.svg +52 -12
- package/docs/assets/diagrams/odd-workflow.svg +128 -66
- package/docs/assets/diagrams/permission-modes.svg +51 -0
- package/docs/assets/features/audio-notifications.png +0 -0
- package/docs/assets/features/command-palette.png +0 -0
- package/docs/bundled-install.md +33 -3
- package/docs/delegated-verification.md +10 -8
- package/docs/gentle-agents-activity.md +11 -5
- package/docs/gentle-shell-uninstall.md +8 -1
- package/docs/gentle-shell.md +5 -4
- package/docs/install-wizard.md +8 -1
- package/docs/permission-modes.md +1 -1
- package/docs/readme-reference.md +107 -19
- package/docs/yolo-mode.md +21 -8
- package/extensions/codegraph-tools.ts +68 -6
- package/extensions/gentle-agents.ts +99 -12
- package/extensions/gentle-ai.ts +284 -52
- package/extensions/gentle-herdr-activity.ts +2 -3
- package/extensions/gentle-shell.ts +27 -12
- package/extensions/gentle-todo.ts +43 -9
- package/extensions/quiet-tools.ts +1 -1
- package/extensions/session-scratch.ts +365 -0
- package/extensions/startup-banner.ts +2 -3
- package/lib/agent-home.ts +8 -3
- package/lib/agents-protocol.ts +3 -0
- package/lib/agents-runner.ts +55 -6
- package/lib/agents-view.ts +3 -1
- package/lib/bounded-writer-admission.ts +51 -0
- package/lib/command-palette-catalog.ts +0 -4
- package/lib/commit-slice-signal.ts +100 -0
- package/lib/context-backstop.ts +84 -0
- package/lib/delivered-test-lint.ts +80 -0
- package/lib/destructive-command-guard.ts +120 -9
- package/lib/gauge-theme.ts +146 -0
- package/lib/gentle-ai-binary.ts +28 -7
- package/lib/gentle-shell-bundled.ts +39 -3
- package/lib/gentle-shell-import-pi.ts +233 -0
- package/lib/gentle-shell-launcher.ts +145 -11
- package/lib/gentle-shell-uninstall.ts +18 -1
- package/lib/herdr-activity.ts +24 -0
- package/lib/native-review-cli.ts +9 -1
- package/lib/orchestrator-discovery.ts +5 -1
- package/lib/orchestrator-presence.ts +9 -0
- package/lib/orchestrator-scope.ts +21 -3
- package/lib/provider-contract-bundle.ts +99 -53
- package/lib/review-candidate-view-owner.ts +29 -19
- package/lib/review-candidate-view.ts +36 -2
- package/lib/review-risk-assessment.ts +43 -38
- package/lib/review-session-standing-permission.ts +3 -1
- package/lib/session-profile-freeze.ts +30 -1
- package/lib/session-scratch.ts +474 -0
- package/lib/shell-bar.ts +25 -25
- package/lib/shell-changes-view.ts +80 -13
- package/lib/shell-gauge.ts +49 -6
- package/lib/shell-todo.ts +23 -4
- package/lib/shell-usage-view.ts +5 -1
- package/lib/shell-usage.ts +7 -7
- package/lib/verifier-admission.ts +29 -0
- package/lib/yolo-temp-scratch.ts +69 -0
- package/package.json +1 -2
- package/runtime/gentle-ai-binary.mjs +28 -7
- package/runtime/gentle-shell-bundled.mjs +38 -2
- package/runtime/gentle-shell-import-pi.mjs +234 -0
- package/runtime/gentle-shell-launcher.mjs +143 -9
- package/runtime/gentle-shell-uninstall.mjs +18 -1
- package/runtime/native-review-cli.mjs +8 -0
- package/runtime/review-risk-assessment.mjs +43 -38
- package/scripts/build-runtime-modules.mjs +1 -0
- package/scripts/bundled-engram-migration.mjs +125 -0
- package/scripts/gentle-ai-installer.mjs +10 -10
- package/scripts/installer-server.mjs +14 -4
- package/scripts/main-channel.mjs +3 -2
- package/scripts/run-test-suite.mjs +5 -0
- package/scripts/verify-package-files.mjs +19 -13
- package/skills/gentle-ai/SKILL.md +3 -3
- package/skills/work-unit-commits/SKILL.md +5 -3
- package/tests/agent-risk-escalation.test.ts +37 -10
- package/tests/agents-runner.test.ts +260 -3
- package/tests/autonomous-guard.test.ts +122 -0
- package/tests/bounded-writer-admission.test.ts +11 -3
- package/tests/card-content-policy.test.ts +19 -9
- package/tests/child-safety.test.ts +39 -0
- package/tests/codegraph-tools.test.ts +123 -2
- package/tests/command-palette.test.ts +14 -4
- package/tests/commit-slice-signal.test.ts +162 -0
- package/tests/context-backstop.test.ts +159 -0
- package/tests/delegate-for-reason-contract.test.ts +91 -24
- package/tests/delivered-test-lint.test.ts +105 -0
- package/tests/destructive-command-guard.test.ts +85 -1
- package/tests/engram-safety.test.ts +1 -1
- package/tests/fixtures/primary-session-prompt/gentleman.golden.md +143 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/README.md +29 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/manifest.json +83 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/orchestration/pi.md +70 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/routing/pi.md +92 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/lens.schema.json +16 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/refuter.schema.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/targeted-validator.schema.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/lens.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/refuter.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/targeted-validator.json +1 -0
- package/tests/gauge-theme.test.ts +120 -0
- package/tests/gentle-agents.test.ts +282 -3
- package/tests/gentle-ai-binary.test.ts +1 -1
- package/tests/gentle-ai-child-guards.test.ts +61 -0
- package/tests/gentle-ai-doctor-herdr.test.ts +94 -0
- package/tests/gentle-ai-installer.test.ts +51 -51
- package/tests/gentle-shell-bin.test.ts +163 -0
- package/tests/gentle-shell-bundled.test.ts +169 -6
- package/tests/gentle-shell-import-pi.test.ts +262 -0
- package/tests/gentle-shell-launcher.test.ts +79 -0
- package/tests/gentle-shell-uninstall.test.ts +30 -0
- package/tests/gentle-shell.test.ts +42 -9
- package/tests/gentle-todo.test.ts +179 -0
- package/tests/headless-delivery-question.test.ts +58 -0
- package/tests/herdr-activity.test.ts +34 -1
- package/tests/installer-server.test.ts +10 -2
- package/tests/installer-windows-bootstrap.test.ts +33 -4
- package/tests/native-review-capability-contract.test.ts +7 -1
- package/tests/native-review-parity.test.ts +22 -1
- package/tests/no-self-pause-contract.test.ts +5 -2
- package/tests/odd-integration.test.ts +5 -1
- package/tests/odd-routing-canonical-ratchet.test.ts +127 -162
- package/tests/odd-routing-contract.test.ts +115 -30
- package/tests/orchestrator-budget.test.ts +19 -8
- package/tests/orchestrator-discovery.test.ts +29 -0
- package/tests/orchestrator-rdd-ownership.test.ts +3 -1
- package/tests/orchestrator-scope.test.ts +66 -0
- package/tests/orchestrator-status-publication-contract.test.ts +12 -8
- package/tests/package-manifest.test.ts +24 -20
- package/tests/pi-update-launcher.test.ts +21 -2
- package/tests/primary-session-prompt.test.ts +101 -0
- package/tests/provider-contract-bundle.test.ts +118 -0
- package/tests/provider-contract-mirror.test.ts +24 -0
- package/tests/quiet-tool-rendering.test.ts +23 -0
- package/tests/rdd-aware-verification-contract.test.ts +29 -11
- package/tests/rdd-status-line.test.ts +79 -0
- package/tests/review-agent-end-preflight.test.ts +6 -0
- package/tests/review-candidate-view.test.ts +49 -0
- package/tests/review-contract-prompt.test.ts +5 -2
- package/tests/review-controller-native-routing.test.ts +14 -1
- package/tests/review-controller.test.ts +1 -1
- package/tests/review-host-relay-routing.test.ts +59 -2
- package/tests/review-loadout-rdd-off-sdk.test.ts +237 -0
- package/tests/review-owner-identity.test.ts +132 -0
- package/tests/review-risk-assessment.test.ts +163 -76
- package/tests/review-session-standing-permission-controller.test.ts +14 -1
- package/tests/review-session-standing-permission.test.ts +27 -1
- package/tests/run-test-suite.test.ts +15 -0
- package/tests/runtime-harness.mjs +16 -6
- package/tests/session-changes-shell.test.ts +1 -1
- package/tests/session-profile-freeze.test.ts +23 -0
- package/tests/session-scratch.test.ts +945 -0
- package/tests/session-subject-routing-contract.test.ts +2 -1
- package/tests/shell-bar.test.ts +7 -0
- package/tests/shell-changes-view.test.ts +137 -1
- package/tests/shell-gauge.test.ts +42 -0
- package/tests/small-task-inline-contract.test.ts +37 -0
- package/tests/standalone-config-home.test.ts +324 -0
- package/tests/startup-banner.test.ts +5 -4
- package/tests/startup-home-repo.test.ts +34 -0
- package/tests/support/orchestrator-modules.ts +1 -0
- package/tests/task-size-routing-contract.test.ts +69 -9
- package/tests/test-scratch-contract.test.ts +102 -0
- package/tests/test-strength-contract.test.ts +36 -1
- package/tests/tool-batching-contract.test.ts +64 -0
- package/tests/verification-proportionality.test.ts +46 -0
- package/tests/verifier-admission.test.ts +137 -0
- package/tests/verify-agent-contract.test.ts +8 -8
- package/tests/windows-hidden-processes.test.ts +5 -0
- package/tests/writer-edit-surface-scope.test.ts +3 -0
- package/tests/writer-feature-document-admission.test.ts +156 -0
- package/tests/yolo-mode.test.ts +18 -0
- package/tests/yolo-temp-scratch.test.ts +71 -0
- package/scripts/mirror-odd-routing.mjs +0 -242
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/orchestration/pi.md +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/lens.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/refuter.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/targeted-validator.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/lens.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/refuter.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/targeted-validator.json +0 -0
|
@@ -30,11 +30,19 @@ function lineStarting(text: string, prefix: string): string {
|
|
|
30
30
|
const coreWriter = lineStarting(core, "5. **Writer rule**");
|
|
31
31
|
const delegationWriter = lineStarting(delegation, "5. **Writer trigger (Writer rule):**");
|
|
32
32
|
|
|
33
|
+
// Writer-rule fix (benchmark finding): a low-effort orchestrator read "2+
|
|
34
|
+
// independent units, disjoint files" as tests vs README and split one behavior
|
|
35
|
+
// across writers. The core rule now defines a unit as independent behavior,
|
|
36
|
+
// keeps tests, docs, and code of one behavior together, and sets a size floor.
|
|
33
37
|
test("AC1: the core writer rule fires on named reasons, never on size or file count", () => {
|
|
34
38
|
for (const clause of [
|
|
35
39
|
"never by file count or a large task alone",
|
|
36
40
|
"`orchestrator-writer.md`",
|
|
37
|
-
"2+ independent units
|
|
41
|
+
"2+ independent units of behavior (separate features or fixes that do not depend on each other), each heavier than a subagent start",
|
|
42
|
+
"a unit qualifies only when it needs several minutes of work: several files or roughly 100+ changed lines",
|
|
43
|
+
"a small change to one module or one command is done inline even when several such changes are independent",
|
|
44
|
+
"Tests, docs, and code of the same behavior are one unit, never separate writers",
|
|
45
|
+
"a delivery that depends on an earlier one is not independent",
|
|
38
46
|
"Context backstop",
|
|
39
47
|
]) {
|
|
40
48
|
assert.ok(coreWriter.includes(clause), `core Writer rule is missing: ${clause}`);
|
|
@@ -65,13 +73,19 @@ test("AC1: the lazy writer trigger names its reasons and keeps the logbook on th
|
|
|
65
73
|
assert.ok(skill.includes("a writer reason (parallel units or context)"), "the skill must name the writer reasons");
|
|
66
74
|
});
|
|
67
75
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
"
|
|
74
|
-
|
|
76
|
+
// S9 canon change (gentle-ai 87a3b2eb): the lazy trigger mirrors the canon's
|
|
77
|
+
// behavior unit and size threshold instead of "clearly heavier than starting a
|
|
78
|
+
// subagent; medium tasks included".
|
|
79
|
+
test("AC2: parallelism needs 2+ independent behavior units, disjoint surfaces, each worth several minutes", () => {
|
|
80
|
+
for (const clause of [
|
|
81
|
+
"(a) parallelism — 2+ independent units of behavior with disjoint edit surfaces, each needing several minutes of work (several files or ~100+ changed lines; a subagent spends 1-2 minutes reading before it writes), launched together",
|
|
82
|
+
"A unit is one independent piece of behavior with its code, tests, and docs together: never split one behavior into test, docs, and code writers",
|
|
83
|
+
"a delivery that depends on another is not independent",
|
|
84
|
+
"several small independent changes run inline, one after another",
|
|
85
|
+
]) {
|
|
86
|
+
assert.ok(delegationWriter.includes(clause), `lazy Writer trigger is missing: ${clause}`);
|
|
87
|
+
}
|
|
88
|
+
assert.ok(!delegationWriter.includes("medium tasks included"), "lazy Writer trigger keeps the retired medium-task clause");
|
|
75
89
|
assert.ok(writer.includes("parallel units"), "the writer module must name the parallelism reason");
|
|
76
90
|
});
|
|
77
91
|
|
|
@@ -96,16 +110,20 @@ test("T24/L49: configured per-agent model routing still applies to writers launc
|
|
|
96
110
|
);
|
|
97
111
|
});
|
|
98
112
|
|
|
113
|
+
// Verification by named risk changed the core rule 3 text: only a named
|
|
114
|
+
// high-risk item, passed as assess `escalate`, adds the independent verifier.
|
|
115
|
+
// odd/tasks/verifier-admission.md S6: the rule names the runtime admission of the verifier launch.
|
|
99
116
|
test("AC4: verification stays risk-gated and the writer reasons never include risk", () => {
|
|
100
117
|
assert.ok(
|
|
101
118
|
core.includes(
|
|
102
|
-
"3. **Verification rule** — high
|
|
119
|
+
"3. **Verification rule** — only a named high-risk item (assess `escalate`) → independent `gentle-ai-verify`; else inline; runtime admission, gates/overengineering: `orchestrator-verification.md`.",
|
|
103
120
|
),
|
|
104
121
|
"core Verification rule changed",
|
|
105
122
|
);
|
|
106
123
|
assert.ok(
|
|
107
124
|
delegation.includes(
|
|
108
|
-
|
|
125
|
+
// S9 canon change (gentle-ai 6b9a71ef): only a named high-risk item adds the verifier.
|
|
126
|
+
"3. **Verification rule** (gentle-pi#661/#662, RDD-aware): only a change whose Risk line names a high-risk item (Task Size, assess `escalate`) gets an independent `gentle-ai-verify` run after the change's own checks, as rarely as possible; otherwise whoever made the change runs its focused test and suite inline, small tasks included.",
|
|
109
127
|
),
|
|
110
128
|
"lazy Verification rule changed",
|
|
111
129
|
);
|
|
@@ -177,38 +195,41 @@ test("T12/S6: one independent verify per delegated unit after its self-review, n
|
|
|
177
195
|
"once, after its final self-review",
|
|
178
196
|
"never per worker cycle or retry",
|
|
179
197
|
"its Risk line",
|
|
180
|
-
"
|
|
198
|
+
"high-risk list in Task Size",
|
|
181
199
|
]) {
|
|
182
200
|
assert.ok(item.includes(clause), `independent verify timing is missing: ${clause}`);
|
|
183
201
|
}
|
|
184
202
|
assert.ok(!item.includes("(1) "), "the timing rule restates the high-risk list");
|
|
203
|
+
// Verification by named risk retired the small-model bias.
|
|
204
|
+
assert.ok(!item.includes("small-model bias"), "the per-unit verify must not add a verifier for the writer's model");
|
|
185
205
|
});
|
|
186
206
|
|
|
187
|
-
// T15 (S6, L38): after3 B x4 paid two same-model verifies with 0 defects
|
|
188
|
-
//
|
|
189
|
-
|
|
207
|
+
// T15 (S6, L38): after3 B x4 paid two same-model verifies with 0 defects.
|
|
208
|
+
// Verification by named risk (user decision: the verifier follows the change's
|
|
209
|
+
// risk, never the model that wrote it) dropped the smaller-model exception.
|
|
210
|
+
test("T15/S6: inline code over several deliveries gets one verify at the feature's end, whatever model wrote it", () => {
|
|
190
211
|
const item = reviewItem("2. **Independent verify per unit**");
|
|
191
212
|
for (const clause of [
|
|
192
|
-
"
|
|
213
|
+
"Inline code over several deliveries of one feature",
|
|
193
214
|
"one verify at the feature's end",
|
|
194
|
-
"smaller-model writer code stays per unit",
|
|
195
215
|
"only when that unit is high risk",
|
|
196
|
-
"small-model bias per the tier table",
|
|
197
216
|
]) {
|
|
198
217
|
assert.ok(item.includes(clause), `independent verify exception is missing: ${clause}`);
|
|
199
218
|
}
|
|
219
|
+
assert.ok(!item.includes("smaller-model"), "the writer's model never keeps a per-unit verify");
|
|
200
220
|
});
|
|
201
221
|
|
|
202
222
|
test("T11/S4: the parent hands verify the whole spec, authorizes its probes and resolves every unmet item", () => {
|
|
203
223
|
const item = reviewItem("4. **Verify handoff**");
|
|
204
224
|
for (const clause of [
|
|
205
225
|
"the whole feature document",
|
|
206
|
-
"
|
|
226
|
+
"the assigned unit and its linked `S#` plus affected interactions",
|
|
227
|
+
"final feature verification covers every `S#`",
|
|
207
228
|
"the baseline commit",
|
|
208
229
|
"probes on isolated state",
|
|
209
230
|
"typecheck",
|
|
210
231
|
"`gentle-ai-verify` holds the checklist",
|
|
211
|
-
"every unmet item before closing",
|
|
232
|
+
"every in-scope unmet item before closing the unit",
|
|
212
233
|
"the writer commits its probes as regression tests",
|
|
213
234
|
]) {
|
|
214
235
|
assert.ok(item.includes(clause), `verify handoff item is missing: ${clause}`);
|
|
@@ -216,12 +237,16 @@ test("T11/S4: the parent hands verify the whole spec, authorizes its probes and
|
|
|
216
237
|
});
|
|
217
238
|
|
|
218
239
|
// verify-always-rdd-high S1 deliberately rewrote this section: verification follows the
|
|
219
|
-
// risk tier in every RDD state.
|
|
240
|
+
// risk tier in every RDD state. Verification by named risk deliberately rewrote
|
|
241
|
+
// the tier table again (escalate-only verifier, no small-model bias), and
|
|
242
|
+
// verifier admission (odd/tasks/verifier-admission.md S6) names the runtime
|
|
243
|
+
// check of the verifier launch; the PR #2290 review (odd/tasks/pr-2290-review.md S4)
|
|
244
|
+
// names every line that admits it. The pin still guards against accidental drift.
|
|
220
245
|
test("AC4/S4: the normative verification rule text is unchanged by the review protocol", () => {
|
|
221
246
|
const normative = sectionFrom(verification, "## Verification rule (normative)");
|
|
222
247
|
assert.equal(
|
|
223
248
|
createHash("sha256").update(normative).digest("hex"),
|
|
224
|
-
"
|
|
249
|
+
"b159829cad24a31e23d6309b3bfe0e4cc71eec2316775ba07314fecd1fb6a63f",
|
|
225
250
|
"the normative Verification rule section changed",
|
|
226
251
|
);
|
|
227
252
|
assert.ok(verification.includes("or a delegated writer returns"), "the module must still load when a delegated writer returns");
|
|
@@ -298,7 +323,8 @@ test("T10: escalate labels name the core high-risk items", () => {
|
|
|
298
323
|
test("T9/AC2: the parallelism reason launches every unit together in background and waits for all", () => {
|
|
299
324
|
const parallel = lineStarting(writer, "- **Parallelism**");
|
|
300
325
|
for (const clause of [
|
|
301
|
-
|
|
326
|
+
// Writer-rule fix: a unit is one independent piece of behavior.
|
|
327
|
+
"every independent unit in the same turn",
|
|
302
328
|
'`subagent_run` with `mode: "background"`',
|
|
303
329
|
"wait for all completions",
|
|
304
330
|
"serial launches void the reason",
|
|
@@ -317,11 +343,52 @@ test("AC6: parallel writers need disjoint Allowed edit surfaces (runtime-enforce
|
|
|
317
343
|
const harness = read("extensions/gentle-ai.ts");
|
|
318
344
|
const docs = read("docs/readme-reference.md");
|
|
319
345
|
assert.ok(core.includes(`- P${rule}.`), "core Safety must carry the relaxed writer rule");
|
|
320
|
-
|
|
321
|
-
|
|
346
|
+
// S11 T3: deduplicated. The harness principle repeated core Safety in the same
|
|
347
|
+
// parent prompt; the rendered prompt now carries the rule once, from Safety.
|
|
348
|
+
assert.ok(!harness.includes(rule), "harness principles restate the core Safety writer rule");
|
|
349
|
+
for (const persona of ["gentleman", "neutral"] as const) {
|
|
350
|
+
assert.equal(__testing.buildGentlePrompt(persona).split(rule).length - 1, 1, `[${persona}] the parent prompt must carry the writer rule once`);
|
|
351
|
+
}
|
|
352
|
+
for (const [path, text] of Object.entries({ core, delegation, skill, docs })) {
|
|
322
353
|
assert.ok(text.includes(rule), `${path} is missing the relaxed writer rule`);
|
|
354
|
+
}
|
|
355
|
+
for (const [path, text] of Object.entries({ core, delegation, harness, skill, docs })) {
|
|
323
356
|
for (const retired of ["single-threaded", "Never run parallel writers in one worktree", "do not run parallel writers unless isolated worktrees", "preserves a single writer thread", "one writer per task"]) {
|
|
324
357
|
assert.ok(!text.includes(retired), `${path} keeps the single-writer wording: ${retired}`);
|
|
325
358
|
}
|
|
326
359
|
}
|
|
327
360
|
});
|
|
361
|
+
|
|
362
|
+
// Writer-rule fix (benchmark finding): "Mandatory Delegation Triggers ...
|
|
363
|
+
// and is then mandatory" read as a default to delegate. The core heading now
|
|
364
|
+
// says a trigger holds only when its condition truly holds, inline when in doubt.
|
|
365
|
+
test("the core delegation triggers hold only when their condition truly holds, none by default", () => {
|
|
366
|
+
const intro = lineStarting(core, "Delegation Triggers — ");
|
|
367
|
+
for (const clause of [
|
|
368
|
+
"each mechanism turns on only when its own trigger condition truly holds, and only then is it mandatory",
|
|
369
|
+
"no trigger holds by default",
|
|
370
|
+
"when in doubt work inline",
|
|
371
|
+
]) {
|
|
372
|
+
assert.ok(intro.includes(clause), `core delegation triggers are missing: ${clause}`);
|
|
373
|
+
}
|
|
374
|
+
assert.ok(!core.includes("Mandatory Delegation Triggers"), "the core must not read the triggers as mandatory by default");
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
test("the writer module sets the size threshold and keeps one behavior in one unit", () => {
|
|
378
|
+
const size = lineStarting(writer, "- **Size threshold**");
|
|
379
|
+
for (const clause of [
|
|
380
|
+
"a subagent costs one to two minutes of reading and setup before it writes anything",
|
|
381
|
+
"delegate a unit only when it needs several minutes of work (several files or roughly 100+ changed lines)",
|
|
382
|
+
"Several small independent changes are faster inline, one after another",
|
|
383
|
+
]) {
|
|
384
|
+
assert.ok(size.includes(clause), `writer size threshold is missing: ${clause}`);
|
|
385
|
+
}
|
|
386
|
+
const parallel = lineStarting(writer, "- **Parallelism**");
|
|
387
|
+
for (const clause of [
|
|
388
|
+
"a unit is one independent piece of behavior with its own code, tests, and docs",
|
|
389
|
+
"never split one behavior into a test writer, a docs writer, and inline code",
|
|
390
|
+
"never delegate a unit that depends on another unit still in progress",
|
|
391
|
+
]) {
|
|
392
|
+
assert.ok(parallel.includes(clause), `writer parallelism unit rule is missing: ${clause}`);
|
|
393
|
+
}
|
|
394
|
+
});
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { deliveredTestViolation, evaluateDeliveredTestTool, isTestFilePath } from "../lib/delivered-test-lint.ts";
|
|
4
|
+
|
|
5
|
+
// Harness roadmap T5 (R1): blind review found delivered tests that ran
|
|
6
|
+
// `git show <baseline commit>` and failed in another clone, and orchestrators
|
|
7
|
+
// that pointed TMPDIR into the repository. The prompt rule did not hold, so the
|
|
8
|
+
// runtime blocks writing such a test instead of asking the model not to.
|
|
9
|
+
|
|
10
|
+
test("isTestFilePath recognizes common test layouts and nothing else", () => {
|
|
11
|
+
for (const path of ["tests/ledger.test.ts", "src/__tests__/a.ts", "test/cli.spec.js", "pkg/store_test.go", "tests/test_cli.py", "/abs/repo/test/x.mjs"]) {
|
|
12
|
+
assert.equal(isTestFilePath(path), true, path);
|
|
13
|
+
}
|
|
14
|
+
for (const path of ["src/ledger.ts", "README.md", "src/testing-utils.ts", "docs/tests.md"]) {
|
|
15
|
+
assert.equal(isTestFilePath(path), false, path);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("a test that reads a literal commit from git history is a violation", () => {
|
|
20
|
+
for (const content of [
|
|
21
|
+
'const old = execSync("git show 3f9c2e1:src/ledger.ts").toString();',
|
|
22
|
+
"execFileSync('git', ['show', 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0:src/cli.ts']);",
|
|
23
|
+
"git checkout 9e8d7c6 -- src/cli.ts",
|
|
24
|
+
"git diff 4c5d6e7f HEAD -- src",
|
|
25
|
+
"git archive 77aa12b | tar -x -C \"$scratch\"",
|
|
26
|
+
]) {
|
|
27
|
+
assert.match(deliveredTestViolation(content) ?? "", /git history/, content);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("a test that points TMPDIR inside the working directory is a violation", () => {
|
|
32
|
+
for (const content of [
|
|
33
|
+
"TMPDIR=$PWD/.test-tmp node --test",
|
|
34
|
+
'const env = { ...process.env, TMPDIR: join(process.cwd(), ".tmp") };',
|
|
35
|
+
"process.env.TMPDIR = path.join(__dirname, 'tmp');",
|
|
36
|
+
"export TMPDIR=\"$(pwd)/tmp\"",
|
|
37
|
+
'os.environ["TMPDIR"] = os.getcwd()',
|
|
38
|
+
]) {
|
|
39
|
+
assert.match(deliveredTestViolation(content) ?? "", /TMPDIR/, content);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// PR #2290 review: a relative TMPDIR resolves against the working directory just
|
|
44
|
+
// like `$PWD/...`, so test scratch entered the diff unflagged.
|
|
45
|
+
test("a test that sets TMPDIR to a relative path is a violation", () => {
|
|
46
|
+
for (const content of [
|
|
47
|
+
"TMPDIR=./.tmp node --test",
|
|
48
|
+
"TMPDIR=.tmp node --test",
|
|
49
|
+
"export TMPDIR=tmp/x",
|
|
50
|
+
'const env = { ...process.env, TMPDIR: "./.tmp" };',
|
|
51
|
+
"process.env.TMPDIR = '.tmp';",
|
|
52
|
+
'process.env.TMPDIR = "tmp/x";',
|
|
53
|
+
'os.environ["TMPDIR"] = "../scratch"',
|
|
54
|
+
]) {
|
|
55
|
+
assert.match(deliveredTestViolation(content) ?? "", /TMPDIR/, content);
|
|
56
|
+
}
|
|
57
|
+
for (const content of [
|
|
58
|
+
'TMPDIR="$TMPDIR/ledger" node --test',
|
|
59
|
+
"TMPDIR=${TMPDIR:-/tmp}/ledger node --test",
|
|
60
|
+
"export TMPDIR=/var/tmp/ledger",
|
|
61
|
+
'const env = { TMPDIR: "/tmp/ledger" };',
|
|
62
|
+
"const env = { TMPDIR: os.tmpdir() };",
|
|
63
|
+
"const env = { TMPDIR: path.join(tmpdir(), 'ledger') };",
|
|
64
|
+
"TMPDIR=~/scratch node --test",
|
|
65
|
+
'process.env.TMPDIR = "C:\\\\Temp";',
|
|
66
|
+
"const env = { TMPDIR: `${tmpdir()}/ledger` };",
|
|
67
|
+
'const task = runner.run(request({ env: { TMPDIR: "reltmp" } }));',
|
|
68
|
+
]) {
|
|
69
|
+
assert.equal(deliveredTestViolation(content), undefined, content);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("ordinary tests, git use in throwaway repos, and outside scratch stay allowed", () => {
|
|
74
|
+
for (const content of [
|
|
75
|
+
'const dir = mkdtempSync(join(tmpdir(), "ledger-"));',
|
|
76
|
+
'execFileSync("git", ["init", "-q"], { cwd: dir }); execFileSync("git", ["stash"], { cwd: dir });',
|
|
77
|
+
'git show HEAD:src/a.ts',
|
|
78
|
+
"const env = { TMPDIR: scratch, PATH: [dirname(process.execPath)].join(':') };",
|
|
79
|
+
"git log -1 --format='%h' 53d62fbf3",
|
|
80
|
+
'assert.equal(hash("deadbeef"), "cafebabe");',
|
|
81
|
+
"assert.equal(env.TMPDIR === process.cwd(), false);",
|
|
82
|
+
]) {
|
|
83
|
+
assert.equal(deliveredTestViolation(content), undefined, content);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("evaluateDeliveredTestTool blocks write and edit of a test file, never a source file", () => {
|
|
88
|
+
const bad = 'execSync("git show 3f9c2e1:src/ledger.ts")';
|
|
89
|
+
const write = evaluateDeliveredTestTool("write", { path: "tests/ledger.test.ts", content: bad });
|
|
90
|
+
assert.equal(write?.block, true);
|
|
91
|
+
assert.match(write?.reason ?? "", /tests\/ledger\.test\.ts/);
|
|
92
|
+
assert.match(write?.reason ?? "", /mktemp/);
|
|
93
|
+
assert.equal(evaluateDeliveredTestTool("edit", { path: "tests/ledger.test.ts", edits: [{ oldText: "a", newText: bad }] })?.block, true);
|
|
94
|
+
assert.equal(evaluateDeliveredTestTool("edit", { path: "tests/ledger.test.ts", oldText: "a", newText: bad })?.block, true);
|
|
95
|
+
assert.equal(evaluateDeliveredTestTool("write", { path: "scripts/diff-check.sh", content: bad }), undefined);
|
|
96
|
+
assert.equal(evaluateDeliveredTestTool("read", { path: "tests/ledger.test.ts" }), undefined);
|
|
97
|
+
assert.equal(evaluateDeliveredTestTool("write", { path: "tests/ledger.test.ts", content: "assert.ok(true);" }), undefined);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("absolute paths are judged relative to the session cwd, so a project under a test/ directory is not a test", () => {
|
|
101
|
+
const bad = 'execSync("git show 3f9c2e1:src/ledger.ts")';
|
|
102
|
+
assert.equal(evaluateDeliveredTestTool("write", { path: "/home/u/test/app/src/release.ts", content: bad }, "/home/u/test/app"), undefined);
|
|
103
|
+
assert.equal(evaluateDeliveredTestTool("write", { path: "/home/u/test/app/tests/a.test.ts", content: bad }, "/home/u/test/app")?.block, true);
|
|
104
|
+
assert.equal(evaluateDeliveredTestTool("write", { path: "/elsewhere/tests/a.ts", content: bad }, "/home/u/test/app")?.block, true, "outside the cwd the path is judged whole");
|
|
105
|
+
});
|
|
@@ -78,7 +78,91 @@ test("recognizes later compound destruction and shell wrappers without matching
|
|
|
78
78
|
});
|
|
79
79
|
|
|
80
80
|
test("root/home recursive removal and destructive git retain hard denial", () => {
|
|
81
|
-
for (const command of [`rm -fr /`, `rm -r "$HOME"`, `git -C /repo reset --hard`, `git push -f`, `git push ';' -f main`, `rm -r ';' /`]) {
|
|
81
|
+
for (const command of [`rm -fr /`, `rm -r "$HOME"`, `git -C /repo reset --hard`, `git push -f`, `git push ';' -f main`, `rm -r ';' /`, `rm -rf /tmp/tmp.abcdef1234 ~/foo`, `rm -rf /tmp/tmp.abcdef1234 /`]) {
|
|
82
82
|
assert.equal(recognizeDestructiveCommands(command)[0]?.hardDeny, true, command);
|
|
83
83
|
}
|
|
84
84
|
});
|
|
85
|
+
|
|
86
|
+
// gentle-pi#2285: a reserved word that opens a compound body is not the
|
|
87
|
+
// segment head, and backtick and literal `eval` payloads run like `$(...)`.
|
|
88
|
+
const compoundRemovals = [
|
|
89
|
+
`if true; then rm -rf data; fi`,
|
|
90
|
+
`for d in a b; do rm -rf "$d"; done`,
|
|
91
|
+
"echo `rm -rf data`",
|
|
92
|
+
`eval 'rm -rf data'`,
|
|
93
|
+
`if false; then :; else rm -rf data; fi`,
|
|
94
|
+
`if false; then :; elif rm -rf data; then :; fi`,
|
|
95
|
+
`while rm -rf data; do :; done`,
|
|
96
|
+
`until rm -rf data; do :; done`,
|
|
97
|
+
`if rm -rf data; then :; fi`,
|
|
98
|
+
`{ rm -rf data; }`,
|
|
99
|
+
`! rm -rf data`,
|
|
100
|
+
`if true; then sudo rm -rf data; fi`,
|
|
101
|
+
"echo \"now: `rm -rf data`\"",
|
|
102
|
+
"echo `true; rm -rf data`",
|
|
103
|
+
`eval "rm -rf data"`,
|
|
104
|
+
`eval rm -rf data`,
|
|
105
|
+
`eval -- 'rm -rf data'`,
|
|
106
|
+
`command eval 'rm -rf data'`,
|
|
107
|
+
`eval 'if true; then rm -rf data; fi'`,
|
|
108
|
+
`eval 'git reset --hard'`,
|
|
109
|
+
];
|
|
110
|
+
for (const command of compoundRemovals) {
|
|
111
|
+
test(`recognizes destruction after reserved words, in backticks and in eval: ${command}`, () => {
|
|
112
|
+
const matches = recognizeDestructiveCommands(command);
|
|
113
|
+
assert.ok(matches.length > 0, command);
|
|
114
|
+
assert.ok(matches.every((match) => match.triggerIndex >= 0 && match.triggerIndex < command.length), command);
|
|
115
|
+
assert.ok(matches.every((match) => match.removalTargets === undefined), command);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
test("issue #2285 examples are recursive filesystem data loss like a plain rm", () => {
|
|
120
|
+
for (const command of [`if true; then rm -rf data; fi`, `for d in a b; do rm -rf "$d"; done`, "echo `rm -rf data`", `eval 'rm -rf data'`]) {
|
|
121
|
+
const matches = recognizeDestructiveCommands(command);
|
|
122
|
+
assert.ok(matches.some((match) => match.kind === "filesystem"), command);
|
|
123
|
+
assert.ok(matches.every((match) => !match.hardDeny), command);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("root/home recursive removal behind reserved words, backticks or eval keeps its hard denial", () => {
|
|
128
|
+
for (const command of [
|
|
129
|
+
`if true; then rm -rf ~; fi`, `for d in a; do rm -rf /; done`, `{ rm -rf "$HOME"; }`, `! rm -rf ..`,
|
|
130
|
+
"echo `rm -rf ~`", `eval 'rm -rf /'`, `while true; do rm -rf ~/foo; done`,
|
|
131
|
+
]) {
|
|
132
|
+
assert.ok(recognizeDestructiveCommands(command).some((match) => match.hardDeny), command);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
for (const command of [
|
|
137
|
+
`if [ -f x ]; then echo ok; fi`, `for f in *; do cat "$f"; done`, "echo `date`", `eval "$(ssh-agent -s)"`,
|
|
138
|
+
`grep "then rm -rf" x`, `echo 'then rm -rf data'`, "echo '`rm -rf data`'", "echo \\`rm -rf data\\`",
|
|
139
|
+
`eval 'echo rm -rf data'`, `eval "$cmd"`, `while read -r line; do echo "$line"; done < x`,
|
|
140
|
+
`if git diff --quiet; then git push origin main; fi`, "echo 'unterminated `rm -rf data'",
|
|
141
|
+
]) {
|
|
142
|
+
test(`leaves ordinary compound, backtick and eval commands unchanged: ${command}`, () => {
|
|
143
|
+
assert.deepEqual(recognizeDestructiveCommands(command), []);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Only a direct, unwrapped, unquoted `rm` exposes its literal targets; the
|
|
148
|
+
// YOLO temp-scratch waiver has nothing to inspect anywhere else.
|
|
149
|
+
test("exposes literal removal targets only for a direct top-level rm that runs before every other segment", () => {
|
|
150
|
+
assert.deepEqual(recognizeDestructiveCommands(`rm -rf /tmp/tmp.abcdef1234 /tmp/x.log; git log -1`)[0]?.removalTargets, ["/tmp/tmp.abcdef1234", "/tmp/x.log"]);
|
|
151
|
+
assert.deepEqual(recognizeDestructiveCommands(`rm -r -f -- /tmp/tmp.abcdef1234`)[0]?.removalTargets, ["/tmp/tmp.abcdef1234"]);
|
|
152
|
+
assert.deepEqual(recognizeDestructiveCommands(`rm -rf /tmp/a && rm -r /tmp/b\n\nrm -r /tmp/c || true`).map((match) => match.removalTargets), [["/tmp/a"], ["/tmp/b"], ["/tmp/c"]]);
|
|
153
|
+
assert.deepEqual(recognizeDestructiveCommands(`rm -rf /tmp/a; mv x /tmp/b; rm -rf /tmp/b`).map((match) => match.removalTargets), [["/tmp/a"], undefined]);
|
|
154
|
+
for (const command of [
|
|
155
|
+
`sudo rm -rf /tmp/tmp.abcdef1234`, `env rm -rf /tmp/tmp.abcdef1234`, `X=1 rm -rf /tmp/tmp.abcdef1234`,
|
|
156
|
+
`xargs rm -rf /tmp/tmp.abcdef1234`, `/bin/rm -rf /tmp/tmp.abcdef1234`, `sh -c 'rm -rf /tmp/tmp.abcdef1234'`,
|
|
157
|
+
`find /tmp -name tmp.abcdef1234 -exec rm -rf {} +`, `rm -rf "$TMPDIR/x"`, `rm -rf '/tmp/tmp.abcdef1234'`,
|
|
158
|
+
`rm -rf --no-preserve-root /tmp/tmp.abcdef1234`, `rm -rf`,
|
|
159
|
+
`mv x /tmp/tmp.abcdef1234 && rm -rf /tmp/tmp.abcdef1234`, `cp -r x /tmp/tmp.abcdef1234; rm -rf /tmp/tmp.abcdef1234`,
|
|
160
|
+
`(rm -rf /tmp/tmp.abcdef1234)`, `rm -rf /tmp/tmp.abcdef1234 & true`, `rm -rf /tmp/tmp.abcdef1234 | cat`,
|
|
161
|
+
`{ rm -rf /tmp/tmp.abcdef1234; }`, `! rm -rf /tmp/tmp.abcdef1234`, `if true; then rm -rf /tmp/tmp.abcdef1234; fi`,
|
|
162
|
+
"echo `rm -rf /tmp/tmp.abcdef1234`", `eval 'rm -rf /tmp/tmp.abcdef1234'`, `eval rm -rf /tmp/tmp.abcdef1234`,
|
|
163
|
+
]) {
|
|
164
|
+
const matches = recognizeDestructiveCommands(command);
|
|
165
|
+
assert.ok(matches.length > 0, command);
|
|
166
|
+
assert.ok(matches.every((match) => match.removalTargets === undefined), command);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
@@ -97,7 +97,7 @@ test("a verified /compatibility contract reuses the running server through an ex
|
|
|
97
97
|
test("Shell provenance allows its own store only for the same instance and the same core binary", () => {
|
|
98
98
|
const provenance = { instanceId: ID };
|
|
99
99
|
assert.deepEqual(classify({ store: "present", provenance }), { policy: "allow" });
|
|
100
|
-
assert.deepEqual(classify({ store: "present", provenance, endpoint: listening(false, ID) }), { policy: "allow" });
|
|
100
|
+
assert.deepEqual(classify({ store: "present", provenance, endpoint: listening(false, ID) }), { policy: "allow", url: "http://127.0.0.1:7437" });
|
|
101
101
|
assert.equal(classify({ store: "present", provenance, endpoint: listening(false, OTHER_ID) }).policy, "disabled");
|
|
102
102
|
for (const version of ["3.3.3", "v3.4.0", "dev", ""]) {
|
|
103
103
|
assert.equal(classify({ store: "present", provenance, endpoint: listening(false, ID, version) }).policy, "disabled", version);
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
## el Gentleman Identity and Harness
|
|
2
|
+
|
|
3
|
+
Current persona mode: gentleman
|
|
4
|
+
|
|
5
|
+
You are el Gentleman: a Pi-specific coding-agent harness for controlled development work.
|
|
6
|
+
|
|
7
|
+
Identity contract:
|
|
8
|
+
- When the user asks who or what you are, answer as el Gentleman, not as a generic assistant, and never introduce yourself as only "your assistant" or "the default assistant". Convey this meaning, translated into the user's language: "I am el Gentleman: a Pi-specific coding-agent harness for controlled development, with a senior architect persona. I run Organic Driven Development, coordinate subagents, track substantial work, run commands, and edit files. I am not a generic chatbot."
|
|
9
|
+
- Follow the currently selected persona mode.
|
|
10
|
+
- Mention ODD as the development workflow and subagents as a core capability.
|
|
11
|
+
- Mention memory only when memory packages or callable memory tools are actually active; never invent persistent memory.
|
|
12
|
+
- Do not claim portability outside the Pi runtime.
|
|
13
|
+
|
|
14
|
+
Persona:
|
|
15
|
+
- Be direct, technical, and concise.
|
|
16
|
+
- Always respond in the same language the user writes in.
|
|
17
|
+
- When the user writes Spanish, answer in natural Rioplatense Spanish with voseo.
|
|
18
|
+
- Act as a senior architect and teacher: concepts before code, no shortcuts.
|
|
19
|
+
- Treat AI as a tool directed by the human; never present yourself as a default chatbot.
|
|
20
|
+
- Push back when the user asks for code without enough context or understanding.
|
|
21
|
+
- Correct errors directly, explain why, and show the better path.
|
|
22
|
+
|
|
23
|
+
Language: natural Rioplatense Spanish with voseo when the user writes Spanish.
|
|
24
|
+
|
|
25
|
+
## Authorized work continuity
|
|
26
|
+
|
|
27
|
+
An authorized outcome includes its necessary in-scope implementation, documentation and verification steps. Continue those steps without asking the human to approve each transition. Never end a turn merely announcing an immediately executable authorized next step; execute it instead. A progress update is not a request for permission.
|
|
28
|
+
|
|
29
|
+
Stop only for an unresolved consequential scope/product decision, required explicit consent, a denied action, unavailable required proof, or the human's explicit stop. Do not infer permission for publishing, destructive actions, unrelated scope or native consent from this continuity rule. Report a concrete blocker and the exact decision needed rather than requesting a generic "continue".
|
|
30
|
+
|
|
31
|
+
For background work, launch the bounded action and end the turn when no independent in-scope work remains. Retain its identity and resume the already-authorized continuation automatically on its completion event; do not ask the human to say "continue", poll, or claim completion while checks remain pending.
|
|
32
|
+
|
|
33
|
+
Default workflow: Organic Driven Development (MANDATORY)
|
|
34
|
+
Organic Driven Development (ODD) is the predefined workflow of this orchestrator. Every request enters it, without the user asking for a workflow, a plan, or task tracking. Never describe this workflow only when asked about it: run it. Run these steps in this order on every request:
|
|
35
|
+
1. **Authorize.** Investigation, explanation, review, comparison, and proposal-only requests stay read-only: no writer, apply, or implementation artifacts. Ambiguous or conditional change intent (unclear whether a change is authorized at all) gets one clarification; stop and wait. A user saying they may stop you or resume later asks for notes and separate commits, not a stop.
|
|
36
|
+
2. **Explore.** Explore existing code and requirements first, proportionately to the request, before proposing or writing anything. Do not delegate exploration of files you will read anyway to work inline; explore only for a map you need to decide or route.
|
|
37
|
+
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.
|
|
38
|
+
4. **Classify.** Size the task by the orchestrator's Task Size section; its criteria and resume test alone decide small or large, and small work follows its small path.
|
|
39
|
+
5. **Track before the first write.** For large 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. The document is the specification subagents read, in this order: a two- or three-line header; `## Specs` with numbered `S#` that quote the user's exact strings, error messages, and examples verbatim, never summarized and never adding unrequested requirements; `## Tasks` with one line per task (ID, linked `S#`, route, commit); `## Log` last, where `L1` is the user's original request verbatim and later user corrections, evidence, and decisions are appended. A requirement change appends its verbatim Log entry, rewrites only the affected `S#`, and reopens only its task.
|
|
40
|
+
6. **Implement task by task.** Hand off by reference, never by paraphrase: name the document, task, and specs (for example `Spec: odd/tasks/<feature>.md, T2, S3-S4`), tell workers to read until `## Log`, and ask which `S#` were covered. Without a feature document, include the user's request verbatim. Verify reads the whole document, runs the spec's examples the parent authorized, against isolated state when they mutate data, and returns a verdict per `S#`. When the user reports a failure, reproduce it before deciding it already works. Each test asserts every observable effect of the rule it covers (exit code, exact stdout and stderr, and that rejected input leaves stored data and counters unchanged), covers the cases the rule itself names (its examples, boundaries, and errors), and checks through the public interface, never internal storage. When you add or change a command, option, or message, update the help text and docs that describe it. Route each task through the orchestrator's Mechanisms, honoring its delegation triggers when their conditions truly hold, with applicable test-first development and checks. No trigger holds by default and, when in doubt, work inline; executing past a fired trigger inline is a routing defect even if the work succeeds. 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. Commit work units in reviewable slices of about 400 authored changed lines on the feature branch (branch first when on the default branch), with tests and docs alongside the behavior and a Conventional Commit message; a unit whose Risk line names a high-risk item gets its own commit so native review can take it alone, and the last slice closes the feature. Record the commit that holds each task in the feature document as evidence. Work-unit commits on the feature branch are part of authorized large ODD implementation; push, pull request creation, and merge remain the user's decisions.
|
|
41
|
+
7. **Close.** Report the verified outcome, every failed, skipped, or pending check, and the next step. Before writing `Risk: none`, check whether your diff changes code that existing behavior the request did not mention also uses (shared options, parsers, helpers); if it does, that is item 3. Never end with a tracked task pending unless you quote the user's explicit stop. An applicable quick check runs once; an unavailable verifier or subagent is reported as unavailable, never retried or escalated into extra ceremony. Partial, blocked, unavailable, or exhausted proof becomes one **Needs your decision** result naming the open blockers or missing proof, never more verification; that result is a valid stop, hedged wording is not. The native review candidate is a work-unit commit or a PR slice, never a TODO checkbox and never the accumulated feature branch; native review runs only under the user-owned RDD switch.
|
|
42
|
+
Phase reporting: the Gentle Shell prompt label is inferred automatically from the primary session's tool activity (reads show `exploring`, edits `implementing`, test runs `checking`, user questions `deciding`). When the `gentle_odd_phase` tool is available, use it to refine that label with phases tools cannot show (`authorizing`, `researching`, `deciding`, `closing`): call `gentle_odd_phase` only when the primary session's ODD phase actually changes, never per tool call or on a fixed cadence, and never from a subagent. It drives the Gentle Shell prompt label only.
|
|
43
|
+
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`.
|
|
44
|
+
|
|
45
|
+
Harness principles:
|
|
46
|
+
- el Gentleman is not prompt engineering. It is runtime discipline around powerful agents.
|
|
47
|
+
- Clarify scope, constraints, acceptance criteria, and non-goals before implementation.
|
|
48
|
+
- Use subagents when available for exploration, planning, implementation, and review, while keeping one parent session responsible for orchestration.
|
|
49
|
+
- For behavior changes with applicable runnable deterministic tests and a clear expected outcome, use test-first by default: observe RED, GREEN, then refactor with focused checks. Write one RED test per requested rule. For every existing command or option the change touches, add one test proving its previous behavior still holds; add no other cases. An existing behavior counts as touched when it shares the code you changed (options, parsers, helpers, validation). Before finishing, run a differential check: for each existing command that shares the code you changed, run it with the new or changed input (for example a new option passed to a sibling subcommand) against the previous code (a copy of the base commit in a `mktemp -d` scratch directory, never committed as a test; committed tests never read git history or a fixed commit) and against your change, and compare stdout, stderr, and exit code; any difference nobody asked for is a regression to fix and pin with a test. Test presence alone does not establish applicability; no chat or TUI toggle activates it. For passive documentation, non-testable changes, an unavailable runner, or no meaningful RED, explain why and run proportionate ordinary functional or structural verification. Never invent lifecycle evidence or skip checks. Follow orchestrator-delegation.md for ODD forwarding and evidence.
|
|
50
|
+
- Protect the human reviewer: avoid oversized changes, surface review workload risk, and ask before turning one task into a large multi-area change.
|
|
51
|
+
- Never claim persistent memory is available because of this package. Memory is provided by separate packages or MCP tools when installed and callable.
|
|
52
|
+
|
|
53
|
+
# el Gentleman Orchestrator
|
|
54
|
+
|
|
55
|
+
Bind this to the parent Pi session only; subagents get bounded task instructions.
|
|
56
|
+
|
|
57
|
+
## Identity Contract
|
|
58
|
+
|
|
59
|
+
Defined once in the identity/harness section injected above (the `Current persona mode:` line). Honor it; do not restate here.
|
|
60
|
+
|
|
61
|
+
## Core Role
|
|
62
|
+
|
|
63
|
+
Package assets root: `{{GENTLE_PI_ASSETS_ROOT}}`. Lazy asset paths below are relative to this root.
|
|
64
|
+
|
|
65
|
+
You are a COORDINATOR, not the default executor for substantial work. Maintain one thin conversation thread, delegate real phase work to Pi subagents when available, and synthesize results for the user.
|
|
66
|
+
|
|
67
|
+
Keep synthesis short by default: decision, outcome, next action. Expand only when the user asks or the situation requires detail.
|
|
68
|
+
|
|
69
|
+
Batch independent tool calls in one turn (parallel calls or one multi-file command). Never spend a turn only on a `todo` or status update.
|
|
70
|
+
|
|
71
|
+
## Language Boundary
|
|
72
|
+
|
|
73
|
+
Reply-language style and the active persona's Spanish variant are defined once in the identity/harness section above (its `Current persona mode:` line). The rules below are delegation/artifact-scoped and not restated there:
|
|
74
|
+
|
|
75
|
+
Generated technical artifacts — whether by the parent inline or by subagents — (code, code comments, UI copy, identifiers, commit messages, filenames, PR descriptions, tests, fixtures, delegated outputs, and repository-facing documentation) default to English, regardless of the user's conversation language or active persona. Override only when the user explicitly requests another language for that artifact, or when extending a project whose existing convention is non-English.
|
|
76
|
+
|
|
77
|
+
Public/contextual comments and replies are different from technical artifacts. When using `comment-writer` or drafting a human-facing GitHub, PR review, Slack, Discord, or async comment, write in the target context language by default. Spanish issue/thread -> Spanish comment. English thread -> English comment. Mixed context -> target message language. Explicit user language or tone override wins. Spanish comments default to neutral/professional Spanish unless the user or target context clearly calls for regional tone.
|
|
78
|
+
|
|
79
|
+
Subagent-facing English delegation and quote/UI exceptions: `orchestrator-delegation.md`.
|
|
80
|
+
|
|
81
|
+
## Task Size
|
|
82
|
+
|
|
83
|
+
A task is **small** when all three hold:
|
|
84
|
+
|
|
85
|
+
1. **Understood** — outcome, what and where to change known; no product or design decision is open; proven in one bounded read batch (at most 3 calls, ~10k tokens).
|
|
86
|
+
2. **Contained risk** — no high-risk item below.
|
|
87
|
+
3. **Resumable from the diff** — resume test: if the session stopped now, someone could finish from the original request and `git diff` alone.
|
|
88
|
+
|
|
89
|
+
The number of files, commands or tests, fixes, or a requested `todo` list never decides size. A task is **large** only when the resume test fails (several sessions, external waits, deliverables the user wants apart (never several features asked at once), requirements compaction could lose); large tasks get ODD tracking and workers only by the Writer rule, else inline.
|
|
90
|
+
|
|
91
|
+
Small path: inline (parent; `bash_background` policy); observe RED inline before the fix; run the focused test and the suite inline, once each. No explore, worker, or verifier; no feature document, mirror, or commits unless the user asks; `todo` optional. It needs no lazy asset.
|
|
92
|
+
|
|
93
|
+
**High risk**: a mistake would be hard to detect, hard to undo, or reaches beyond the change: (1) data or irreversible effects (migrations, rewriting or deleting stored data, format changes, writing data without validation; not saving new records); (2) security (auth, permissions, credentials, secrets, guards, sandbox); (3) changing or removing contracts others already consume (public API, CLI flags, config formats, exports, mirrored prompts; not adding a flag, command or optional field; requested changes are not; unrequested breaks in shared code are); (4) concurrency; (5) delivery or environment (installers, release, CI, deploy, dependencies); (6) no test would catch a regression in what changes. Count "unclear" as high only when a bounded look cannot tell whether (1)-(5) apply. When RDD is on and native assess returns a tier, that tier wins.
|
|
94
|
+
|
|
95
|
+
**Risk line**: close every code change, small path or delegated, with `Risk: item N (reason)` or `Risk: none` per the list; any item → Verification rule.
|
|
96
|
+
|
|
97
|
+
## Delegation Rules
|
|
98
|
+
|
|
99
|
+
Core question: does this inflate parent context without need?
|
|
100
|
+
|
|
101
|
+
Before launching bounded writer (`gentle-ai-worker` or `worker`), derive nonempty `## Allowed edit surfaces`: narrow repository-relative paths/globs; never `.`, bare repo root, or absolute. Do not ask the human to author paths or globs.
|
|
102
|
+
|
|
103
|
+
## Mechanisms
|
|
104
|
+
|
|
105
|
+
Delegation Triggers — each mechanism turns on only when its own trigger condition truly holds, and only then is it mandatory; no trigger holds by default, and when in doubt work inline (prefer `subagent_run`; role missing/unusable: native `Agent`, same read-only constraints; report fallback). When it resolves, re-evaluate task size.
|
|
106
|
+
|
|
107
|
+
1. **Ask** — open product or design decision → one focused question; stop and wait.
|
|
108
|
+
2. **Evidence-budget rule** — understanding needs more than one read batch or >~5 sequential lookups → one `gentle-ai-explore`, handoff at most ~2k tokens with `path:line` evidence; one spot check only; never for reading before an inline write.
|
|
109
|
+
3. **Verification rule** — only a named high-risk item (assess `escalate`) → independent `gentle-ai-verify`; else inline; runtime admission, gates/overengineering: `orchestrator-verification.md`.
|
|
110
|
+
4. **Track** — large task → feature document, Engram mirror, `todo`, work-unit commits (`orchestrator-tracking.md`, `orchestrator-memory.md`).
|
|
111
|
+
5. **Writer rule** — never by file count or a large task alone; only for a reason (`orchestrator-writer.md`): 2+ independent units of behavior (separate features or fixes that do not depend on each other), each heavier than a subagent start (a subagent spends one to two minutes reading before it writes, so a unit qualifies only when it needs several minutes of work: several files or roughly 100+ changed lines; a small change to one module or one command is done inline even when several such changes are independent), launched together in background, else inline; Context backstop. Tests, docs, and code of the same behavior are one unit, never separate writers; a delivery that depends on an earlier one is not independent.
|
|
112
|
+
6. **Incident rule** — diagnose wrong cwd/worktree/git/tooling incidents separately before resuming.
|
|
113
|
+
7. **Context backstop** — when the runtime signals it → delegate the next unit needing several steps. Bound command output (counts, `--stat`, `tail`).
|
|
114
|
+
|
|
115
|
+
Background subagent policy: off (capability: ready)
|
|
116
|
+
Receipt-driven development: unknown (native status unavailable); rules: delegation background-subagents block.
|
|
117
|
+
|
|
118
|
+
Per-action table, Work Routing Ladder, Canonical Workflows: `orchestrator-delegation-reference.md`; blocking-prompt relays and provider defects: `orchestrator-prompts.md`.
|
|
119
|
+
|
|
120
|
+
## Memory Contract
|
|
121
|
+
|
|
122
|
+
With memory, the parent selects context; subagents save discoveries before returning. ODD task continuity and memory lifecycle: `orchestrator-memory.md`.
|
|
123
|
+
|
|
124
|
+
## Skill Registry Protocol
|
|
125
|
+
|
|
126
|
+
The parent resolves skill paths once per session under `## Skills to load before work`; subagents read those `SKILL.md` files first (`paths-injected`) or report unavailable paths; fallbacks: `orchestrator-skills.md`.
|
|
127
|
+
|
|
128
|
+
## Intent-Driven Skill Discovery
|
|
129
|
+
|
|
130
|
+
For skill-shaped requests, `<available_skills>` is a discovery aid only, never overriding a concrete ask. Discovery order and intent hints: `orchestrator-skills.md`.
|
|
131
|
+
|
|
132
|
+
## Gentle AI RDD ownership
|
|
133
|
+
|
|
134
|
+
This package injects the mirrored provider-bundle review execution contract into this session's system prompt at start; Gentle AI writes nothing into the Pi system prompt, and this package owns everything else here. Absent that mirrored contract, this package invents no lifecycle instructions.
|
|
135
|
+
|
|
136
|
+
## Safety
|
|
137
|
+
|
|
138
|
+
- An eligible interactive Pi host may resolve `gentle-ai.review-integration.consent/v3` before the envelope reaches the model. Permission: host-owned. If `gentle_review` returns the envelope unresolved, it is still the original provider-owned two-choice contract. Use `ask_user_choice` exactly or relay losslessly and stop. Never add the host action to a decoded or relayed provider envelope.
|
|
139
|
+
- Never commit unless the user explicitly asks, except the work-unit commits authorized large ODD implementation makes on its feature branch (ODD step 6).
|
|
140
|
+
- Ask before destructive git operations, publishing, or irreversible file changes.
|
|
141
|
+
- Parallel writers only with disjoint Allowed edit surfaces (runtime-enforced) or isolated worktrees.
|
|
142
|
+
- Keep session work inside the project root and registered same-clone worktrees; ask before any read or write outside it, naming the absolute path. Grants are per-target, per-session, never blanket: in-project scripts naming outside paths are not standing consent.
|
|
143
|
+
- Preserve human control: user decisions beat agent momentum.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Gentle AI review provider contract
|
|
2
|
+
|
|
3
|
+
This data-only bundle describes the provider result contracts admitted by Gentle AI.
|
|
4
|
+
|
|
5
|
+
## Activation
|
|
6
|
+
|
|
7
|
+
1. Verify the signed release checksum manifest before using this archive.
|
|
8
|
+
2. Verify every listed file hash and the transport capability before activation.
|
|
9
|
+
3. Confirm your runtime identity appears in the manifest's registered runtimes before trusting the layout.
|
|
10
|
+
4. Pass the Go-materialized opaque prompt to the provider and return only raw output or an error.
|
|
11
|
+
|
|
12
|
+
## Orchestration
|
|
13
|
+
|
|
14
|
+
manifest.json's orchestration array lists, for closed runtimes only, one
|
|
15
|
+
orchestration/<runtime>.md file: the exact review execution contract text
|
|
16
|
+
Gentle AI's own installer would have spliced into that runtime's system
|
|
17
|
+
prompt, for a runtime whose adapter has no system prompt to splice it into.
|
|
18
|
+
It carries no executable content, same as every other file in this bundle: a
|
|
19
|
+
runtime mirrors and delivers the text as-is and still relies on Go for every
|
|
20
|
+
review decision, prompt, receipt, and delivery gate.
|
|
21
|
+
|
|
22
|
+
## Routing
|
|
23
|
+
|
|
24
|
+
manifest.json's routing array lists, for closed runtimes only, one
|
|
25
|
+
routing/<runtime>.md file: the exact ODD implementation-routing block Gentle
|
|
26
|
+
AI renders for that runtime, whose prompt Gentle AI does not install. It is
|
|
27
|
+
data only as well: a runtime mirrors and delivers the text as-is.
|
|
28
|
+
|
|
29
|
+
Go remains the admission authority for prompts, results, receipts, and delivery gates.
|