gentle-pi 2.2.0 → 2.4.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.
Files changed (160) hide show
  1. package/README.md +76 -79
  2. package/assets/agents/review-readability.md +0 -2
  3. package/assets/agents/review-reliability.md +0 -2
  4. package/assets/agents/review-resilience.md +0 -2
  5. package/assets/agents/review-risk.md +0 -2
  6. package/assets/agents/sdd-apply.md +5 -3
  7. package/assets/agents/sdd-explore.md +1 -0
  8. package/assets/agents/sdd-proposal.md +2 -0
  9. package/assets/agents/sdd-research.md +54 -0
  10. package/assets/agents/sdd-status.md +4 -4
  11. package/assets/agents/sdd-tasks.md +2 -3
  12. package/assets/agents/sdd-verify.md +21 -1
  13. package/assets/chains/sdd-full.chain.md +1 -1
  14. package/assets/chains/sdd-verify.chain.md +1 -1
  15. package/assets/orchestrator-delegation.md +56 -249
  16. package/assets/orchestrator-memory.md +2 -0
  17. package/assets/orchestrator.md +20 -40
  18. package/assets/sdd-orchestrator-workflow.md +30 -37
  19. package/assets/support/sdd-status-contract.md +7 -7
  20. package/contracts/review-integration/v1/schemas/transition-execution.schema.json +42 -0
  21. package/contracts/review-integration/v2/schemas/last-event-closure.schema.json +66 -0
  22. package/contracts/review-integration/v2/schemas/opencode-provider-role.schema.json +14 -0
  23. package/contracts/review-provider-contract-mirror/provider-contract.lock.json +8 -7
  24. package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/README.md +10 -0
  25. package/contracts/review-provider-contract-mirror/v1.2.0/bundle/manifest.json +74 -0
  26. package/contracts/review-provider-contract-mirror/v1.2.0/bundle/orchestration/pi.md +53 -0
  27. package/contracts/review-provider-contract-mirror/v1.2.0/bundle/schemas/targeted-validator.schema.json +1 -0
  28. package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/generated/provider-capabilities.baseline.json +9 -2
  29. package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/generated/provider-roles.baseline.json +2 -2
  30. package/docs/native-authority-architecture.md +9 -11
  31. package/docs/review-integration.md +27 -373
  32. package/extensions/ask-user-choice.ts +151 -0
  33. package/extensions/gentle-ai.ts +2349 -3785
  34. package/extensions/quiet-tools.ts +515 -32
  35. package/extensions/sdd-init.ts +4 -8
  36. package/lib/agent-home.ts +8 -0
  37. package/lib/gentle-ai-renderer.ts +70 -0
  38. package/lib/model-routing-authority.ts +133 -0
  39. package/lib/native-review-cli.ts +387 -940
  40. package/lib/opaque-pi-reviewer-adapter.ts +284 -0
  41. package/lib/provider-contract-bundle.ts +88 -6
  42. package/lib/review-candidate-view.ts +341 -132
  43. package/lib/review-host-relay.ts +324 -96
  44. package/lib/review-integration-v2.ts +868 -307
  45. package/lib/review-last-event-controller.ts +35 -0
  46. package/lib/sdd-preflight.ts +179 -68
  47. package/lib/sdd-status.ts +66 -111
  48. package/lib/terminal-theme.ts +1 -1
  49. package/package.json +83 -82
  50. package/runtime/gentle-ai-binary.mjs +1 -1
  51. package/runtime/native-review-cli.mjs +337 -890
  52. package/runtime/review-integration-v2.mjs +816 -255
  53. package/runtime/review-relay-contract.mjs +1 -1
  54. package/scripts/{build-git-commit-transaction-runner.mjs → build-runtime-modules.mjs} +4 -5
  55. package/scripts/gentle-ai-installer.mjs +75 -22
  56. package/scripts/maintainer/provider-relay-matrix.mjs +195 -11
  57. package/scripts/test-packed-runner.mjs +4 -7
  58. package/scripts/verify-package-files.mjs +23 -23
  59. package/skills/_shared/review-ledger-contract.md +14 -14
  60. package/skills/chained-pr/SKILL.md +3 -0
  61. package/skills/cognitive-doc-design/SKILL.md +1 -1
  62. package/skills/comment-writer/SKILL.md +1 -1
  63. package/skills/gentle-ai/SKILL.md +7 -74
  64. package/skills/issue-creation/SKILL.md +53 -93
  65. package/skills/judgment-day/SKILL.md +5 -7
  66. package/skills/rdd-defect-workflow/SKILL.md +3 -3
  67. package/skills/release/SKILL.md +3 -3
  68. package/skills/skill-registry/SKILL.md +1 -1
  69. package/skills/work-unit-commits/SKILL.md +3 -1
  70. package/tests/artifact-language.test.ts +24 -11
  71. package/tests/ask-user-choice.test.ts +264 -0
  72. package/tests/codegraph-tools.test.ts +3 -3
  73. package/tests/crosslane/cross-lane.mjs +15 -1168
  74. package/tests/delegated-key-learnings-contract.test.ts +8 -6
  75. package/tests/devbinary/native-review-parity.devtest.ts +167 -250
  76. package/tests/devbinary/pi-host-relay.devtest.ts +867 -0
  77. package/tests/fixtures/devbinary/last-event-capture-correction-plan.captured.json +10 -0
  78. package/tests/fixtures/devbinary/last-event-capture-refuter-approved.captured.json +20 -0
  79. package/tests/fixtures/devbinary/last-event-capture-refuter-correction-required.captured.json +26 -0
  80. package/tests/fixtures/devbinary/last-event-capture-result-approved.captured.json +8 -0
  81. package/tests/fixtures/devbinary/last-event-capture-result-correction-required.captured.json +26 -0
  82. package/tests/fixtures/devbinary/last-event-capture-validation-approved.captured.json +8 -0
  83. package/tests/fixtures/devbinary/last-event-closure.provenance.md +13 -0
  84. package/tests/fixtures/devbinary/review-acknowledged-v1.captured.json +9 -0
  85. package/tests/fixtures/devbinary/review-acknowledged.provenance.md +31 -0
  86. package/tests/fixtures/devbinary/start-v3-zero-lens-closed.captured.json +21 -0
  87. package/tests/fixtures/native-review-cli/v2.5.0-rc.1/PROVENANCE.txt +15 -0
  88. package/tests/fixtures/provider-contract-bundle/v1.2.0/README.md +22 -0
  89. package/{contracts/review-provider-contract-mirror/v1.1.0/bundle → tests/fixtures/provider-contract-bundle/v1.2.0}/manifest.json +11 -2
  90. package/tests/fixtures/provider-contract-bundle/v1.2.0/orchestration/pi.md +97 -0
  91. package/tests/fixtures/provider-contract-bundle/v1.2.0/schemas/lens.schema.json +16 -0
  92. package/tests/fixtures/provider-contract-bundle/v1.2.0/schemas/refuter.schema.json +1 -0
  93. package/tests/fixtures/provider-contract-bundle/v1.2.0/vectors/lens.json +1 -0
  94. package/tests/fixtures/provider-contract-bundle/v1.2.0/vectors/refuter.json +1 -0
  95. package/tests/fixtures/provider-contract-bundle/v1.2.0/vectors/targeted-validator.json +1 -0
  96. package/tests/gentle-ai-binary.test.ts +83 -13
  97. package/tests/gentle-ai-dev-binary.test.ts +51 -228
  98. package/tests/gentle-ai-installer.test.ts +137 -47
  99. package/tests/gentle-ai.test.ts +821 -131
  100. package/tests/gentle-theme.test.ts +133 -0
  101. package/tests/issue-creation-skill.test.ts +103 -0
  102. package/tests/maintainer/provider-relay.maintest.ts +340 -4
  103. package/tests/model-routing-authority.test.ts +257 -0
  104. package/tests/native-review-capability-contract.test.ts +45 -1
  105. package/tests/native-review-cli.test.ts +586 -962
  106. package/tests/native-review-consent.test.ts +150 -66
  107. package/tests/native-review-parity-runtime.test.ts +80 -359
  108. package/tests/native-review-parity.test.ts +674 -925
  109. package/tests/opaque-pi-reviewer-adapter.test.ts +266 -0
  110. package/tests/orchestrator-budget.test.ts +132 -88
  111. package/tests/orchestrator-rdd-ownership.test.ts +103 -0
  112. package/tests/package-manifest.test.ts +152 -36
  113. package/tests/provider-contract-bundle.test.ts +76 -0
  114. package/tests/provider-contract-mirror.test.ts +19 -0
  115. package/tests/provider-defect-handoff.test.ts +15 -118
  116. package/tests/quiet-tool-rendering.test.ts +1055 -28
  117. package/tests/review-agent-end-preflight.test.ts +408 -0
  118. package/tests/review-authority-recovery-docs.test.ts +1 -2
  119. package/tests/review-candidate-view.test.ts +409 -3
  120. package/tests/review-compact-contract.test.ts +4 -30
  121. package/tests/review-contract-prompt.test.ts +139 -0
  122. package/tests/review-controller-lock-status.test.ts +2 -2
  123. package/tests/review-controller-native-recovery.test.ts +363 -1450
  124. package/tests/review-controller-native-routing.test.ts +1251 -5642
  125. package/tests/review-controller-retired-ops.test.ts +1 -1
  126. package/tests/review-controller-workspace-root.test.ts +222 -71
  127. package/tests/review-controller.test.ts +26 -816
  128. package/tests/review-corrected-finalize-binding.test.ts +112 -153
  129. package/tests/review-dispatch-hydration-gap.test.ts +1 -53
  130. package/tests/review-gate.test.ts +0 -45
  131. package/tests/review-host-relay-restart-parity.test.ts +360 -0
  132. package/tests/review-host-relay-routing.test.ts +328 -91
  133. package/tests/review-host-relay.test.ts +436 -14
  134. package/tests/review-integration-v2-forward.test.ts +623 -553
  135. package/tests/review-integration-v2.test.ts +239 -165
  136. package/tests/review-last-event-closure.test.ts +408 -0
  137. package/tests/review-ledger-contract.test.ts +97 -35
  138. package/tests/review-recovered-lineage-routing.test.ts +0 -47
  139. package/tests/review-relay-transport-agent.test.ts +119 -46
  140. package/tests/review-snapshot.test.ts +3 -2
  141. package/tests/runtime-harness.mjs +362 -196
  142. package/tests/sdd-agent-tools.test.ts +36 -0
  143. package/tests/sdd-preflight.test.ts +81 -15
  144. package/tests/sdd-status.test.ts +109 -110
  145. package/tests/skill-collision-prefixes.test.ts +5 -8
  146. package/tests/writer-edit-surface-scope.test.ts +366 -0
  147. package/themes/Gentleman-Cute.json +94 -0
  148. package/themes/Gentleman-Sexy.json +92 -0
  149. package/lib/git-commit-transaction.ts +0 -861
  150. package/runtime/git-commit-transaction.mjs +0 -862
  151. package/scripts/run-git-commit-transaction.mjs +0 -35
  152. package/tests/fixtures/native-review-cli/v2.1.3/sdd-status-engram.json +0 -139
  153. package/tests/fixtures/native-review-cli/v2.1.3/sdd-status.json +0 -200
  154. package/tests/git-commit-transaction.test.ts +0 -530
  155. /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/schemas/lens.schema.json +0 -0
  156. /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/schemas/refuter.schema.json +0 -0
  157. /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/vectors/lens.json +0 -0
  158. /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/vectors/refuter.json +0 -0
  159. /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/vectors/targeted-validator.json +0 -0
  160. /package/{contracts/review-provider-contract-mirror/v1.1.0/bundle → tests/fixtures/provider-contract-bundle/v1.2.0}/schemas/targeted-validator.schema.json +0 -0
@@ -0,0 +1,366 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtempSync, rmSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import test, { after } from "node:test";
6
+ import type {
7
+ ExtensionAPI,
8
+ ExtensionContext,
9
+ } from "@earendil-works/pi-coding-agent";
10
+ import { createGentleAiExtension } from "../extensions/gentle-ai.ts";
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Bounded writer edit-surface scope guard (issue #484).
14
+ //
15
+ // The guard reads the `## Allowed edit surfaces` section out of the delegated
16
+ // `subagent_run` prompt. A delegated task is a full prompt: the surfaces list
17
+ // is followed by deeper headings (`### Validation`, `#### Return`) and by
18
+ // ordinary prose. The section remains fail-closed through ordinary prose until
19
+ // the next canonical Markdown heading, so following content is always parsed as
20
+ // a surface entry rather than silently discarded. A `context` value carrying
21
+ // only the heading and its lines had nothing following it, which is why the same
22
+ // surfaces were accepted there and rejected in `task`.
23
+ //
24
+ // Every case below runs through the real `tool_call` hook with the exact
25
+ // `subagent_run` input shape, because that is the boundary that rejected.
26
+ // ---------------------------------------------------------------------------
27
+
28
+ const REJECTION =
29
+ "Writer tasks must include the exact Markdown heading `## Allowed edit surfaces` with narrow repository-relative paths or narrow globs, one per line. Every non-empty line belongs to the section until the next canonical Markdown heading and must be a valid surface entry. Paths containing whitespace require whole-entry backticks; begin explanatory prose under the next Markdown heading. The parent must derive or map that canonical block from the delegated task and relaunch the writer; do not accept aliases, and do not ask the human to author paths or globs.";
30
+
31
+ type ToolCallHandler = (
32
+ event: { toolName: string; input: unknown },
33
+ ctx: ExtensionContext,
34
+ ) => Promise<{ block: true; reason: string } | undefined>;
35
+
36
+ const scratchRoots: string[] = [];
37
+
38
+ after(() => {
39
+ for (const dir of scratchRoots) rmSync(dir, { recursive: true, force: true });
40
+ });
41
+
42
+ function dispatchWriter(input: Record<string, unknown>) {
43
+ const handlers = new Map<string, ToolCallHandler>();
44
+ const pi = {
45
+ on(name: string, handler: ToolCallHandler) {
46
+ handlers.set(name, handler);
47
+ },
48
+ events: { emit() {} },
49
+ registerCommand() {},
50
+ registerTool() {},
51
+ } as unknown as ExtensionAPI;
52
+ createGentleAiExtension({ nativeReviewCli: null })(pi);
53
+ const toolCall = handlers.get("tool_call");
54
+ assert.equal(typeof toolCall, "function");
55
+ const cwd = mkdtempSync(join(tmpdir(), "gentle-pi-writer-surfaces-"));
56
+ scratchRoots.push(cwd);
57
+ return toolCall!({ toolName: "subagent_run", input }, {
58
+ cwd,
59
+ hasUI: false,
60
+ ui: { confirm: async () => true },
61
+ } as ExtensionContext);
62
+ }
63
+
64
+ async function assertAccepted(input: Record<string, unknown>, message: string) {
65
+ assert.equal(await dispatchWriter(input), undefined, message);
66
+ }
67
+
68
+ async function assertRejected(input: Record<string, unknown>, message: string) {
69
+ assert.deepEqual(await dispatchWriter(input), { block: true, reason: REJECTION }, message);
70
+ }
71
+
72
+ test("task-scoped surfaces are accepted ahead of a deeper heading", async () => {
73
+ await assertAccepted({
74
+ agent: "gentle-ai-worker",
75
+ mode: "task",
76
+ task: [
77
+ "Fix the decoder.",
78
+ "",
79
+ "## Allowed edit surfaces",
80
+ "- `lib/sdd-status.ts`",
81
+ "- `tests/sdd-status.test.ts`",
82
+ "",
83
+ "### Validation commands",
84
+ "- pnpm test",
85
+ "",
86
+ "#### Return",
87
+ "- one structured envelope",
88
+ ].join("\n"),
89
+ }, "a delegated task ends its surfaces list at the next heading of any level");
90
+ });
91
+
92
+ test("task-scoped surfaces reject trailing prose before the next heading", async () => {
93
+ await assertRejected({
94
+ agent: "gentle-ai-worker",
95
+ mode: "task",
96
+ task: [
97
+ "## Allowed edit surfaces",
98
+ "- `lib/sdd-status.ts`",
99
+ "",
100
+ "Then run the focused test file and report the outcome.",
101
+ ].join("\n"),
102
+ }, "trailing prose must begin under a following Markdown heading");
103
+ });
104
+
105
+ test("canonical headings close the surface section with up to three ASCII spaces", async () => {
106
+ for (const indentation of ["", " ", " ", " "]) {
107
+ await assertAccepted({
108
+ agent: "gentle-ai-worker",
109
+ mode: "task",
110
+ task: [
111
+ "## Allowed edit surfaces",
112
+ "- `lib/sdd-status.ts`",
113
+ `${indentation}### Validation`,
114
+ "node --test",
115
+ ].join("\n"),
116
+ }, `${JSON.stringify(indentation)} indentation closes the section`);
117
+ }
118
+ });
119
+
120
+ test("canonical empty headings with trailing spaces close the surface section", async () => {
121
+ for (const indentation of ["", " "]) {
122
+ await assertAccepted({
123
+ agent: "gentle-ai-worker",
124
+ mode: "task",
125
+ task: [
126
+ "## Allowed edit surfaces",
127
+ "- `lib/sdd-status.ts`",
128
+ `${indentation}### `,
129
+ "Ordinary prose after an empty heading is not a surface entry.",
130
+ ].join("\n"),
131
+ }, `${JSON.stringify(indentation)} indentation closes at an empty heading`);
132
+ }
133
+ });
134
+
135
+ test("pseudo-headings remain inside the surface section and reject dangerous paths", async () => {
136
+ for (const [label, heading] of [
137
+ ["four-space indentation", " ### Validation"],
138
+ ["seven hashes", "####### Validation"],
139
+ ["bare marker", "###"],
140
+ ["missing heading separator", "###Validation"],
141
+ ["tab", "###\tValidation"],
142
+ ["vertical tab", "###\vValidation"],
143
+ ["form feed", "###\fValidation"],
144
+ ["bare carriage return", "###\rValidation"],
145
+ ["NBSP", "###\u00a0Validation"],
146
+ ["em space", "###\u2003Validation"],
147
+ ["Ogham space", "###\u1680Validation"],
148
+ ["line separator", "###\u2028Validation"],
149
+ ["paragraph separator", "###\u2029Validation"],
150
+ ] as const) {
151
+ await assertRejected({
152
+ agent: "gentle-ai-worker",
153
+ mode: "task",
154
+ task: [
155
+ "## Allowed edit surfaces",
156
+ "- `lib/sdd-status.ts`",
157
+ heading,
158
+ "/etc/passwd",
159
+ ].join("\n"),
160
+ }, `${label} pseudo-heading cannot close the surface section`);
161
+ }
162
+ });
163
+
164
+ test("only ASCII spaces may separate Markdown list markers from entries", async () => {
165
+ for (const [label, separator] of [
166
+ ["NBSP", "\u00a0"],
167
+ ["em space", "\u2003"],
168
+ ["Ogham space", "\u1680"],
169
+ ["tab", "\t"],
170
+ ] as const) {
171
+ await assertRejected({
172
+ agent: "gentle-ai-worker",
173
+ mode: "task",
174
+ task: ["## Allowed edit surfaces", `-${separator}\`lib/sdd-status.ts\``].join("\n"),
175
+ }, `${label} cannot separate a list marker and entry`);
176
+ }
177
+ await assertAccepted({
178
+ agent: "gentle-ai-worker",
179
+ mode: "task",
180
+ task: ["## Allowed edit surfaces", "- `lib/sdd-status.ts`", "1. tests/sdd-status.test.ts"].join("\n"),
181
+ }, "ASCII-space bullets and numbered entries remain accepted");
182
+ });
183
+
184
+ test("the documented task shape from issue #484 is accepted", async () => {
185
+ await assertAccepted({
186
+ agent: "gentle-ai-worker",
187
+ mode: "task",
188
+ task: [
189
+ "## Allowed edit surfaces",
190
+ "- `lib/sdd-status.ts`",
191
+ "- `tests/sdd-status.test.ts`",
192
+ "",
193
+ "## Skills to load before work",
194
+ "- `skills/typescript/SKILL.md`",
195
+ ].join("\n"),
196
+ }, "the reported repro shape stays accepted");
197
+ });
198
+
199
+ test("bullet, backtick and plain-line surfaces reach the same decision", async () => {
200
+ const bulleted = ["## Allowed edit surfaces", "- `lib/sdd-status.ts`", "- `tests/sdd-status.test.ts`"].join("\n");
201
+ const plain = ["## Allowed edit surfaces", "lib/sdd-status.ts", "tests/sdd-status.test.ts"].join("\n");
202
+ await assertAccepted({ agent: "gentle-ai-worker", mode: "task", task: bulleted }, "bullets in task are accepted");
203
+ await assertAccepted({ agent: "gentle-ai-worker", mode: "task", task: plain }, "plain lines in task are accepted");
204
+ await assertAccepted(
205
+ { agent: "gentle-ai-worker", mode: "task", task: "Fix the decoder.", context: bulleted },
206
+ "bullets in context are accepted",
207
+ );
208
+ await assertAccepted(
209
+ { agent: "gentle-ai-worker", mode: "task", task: "Fix the decoder.", context: plain },
210
+ "plain lines in context are accepted",
211
+ );
212
+ await assertAccepted(
213
+ { agent: "gentle-ai-worker", mode: "task", task: bulleted, context: plain },
214
+ "the same surfaces in both fields are accepted",
215
+ );
216
+ await assertAccepted(
217
+ {
218
+ agent: "gentle-ai-worker",
219
+ mode: "task",
220
+ task: ["## Allowed edit surfaces", "extensions/**/*.ts", "tests/*.test.ts"].join("\n"),
221
+ },
222
+ "narrow no-space globs remain accepted",
223
+ );
224
+ });
225
+
226
+ test("a list broken by a blank line still validates every entry", async () => {
227
+ await assertRejected({
228
+ agent: "gentle-ai-worker",
229
+ mode: "task",
230
+ task: ["## Allowed edit surfaces", "- `lib/sdd-status.ts`", "", "- `/etc/passwd`"].join("\n"),
231
+ }, "a loose bulleted list cannot smuggle an absolute path past the guard");
232
+ await assertRejected({
233
+ agent: "gentle-ai-worker",
234
+ mode: "task",
235
+ context: ["## Allowed edit surfaces", "lib/sdd-status.ts", "", "/etc/passwd"].join("\n"),
236
+ }, "a loose plain-line list cannot smuggle an absolute path past the guard");
237
+ await assertRejected({
238
+ agent: "gentle-ai-worker",
239
+ mode: "task",
240
+ task: ["## Allowed edit surfaces", "lib/sdd-status.ts", "", "../other-repo/lib/a.ts"].join("\n"),
241
+ }, "a loose plain-line list cannot smuggle parent traversal past the guard");
242
+ });
243
+
244
+ test("an entry hidden below a paragraph is validated, not discarded", async () => {
245
+ await assertRejected({
246
+ agent: "gentle-ai-worker",
247
+ mode: "task",
248
+ task: [
249
+ "## Allowed edit surfaces",
250
+ "- `lib/sdd-status.ts`",
251
+ "",
252
+ "Also authorize the following path.",
253
+ "- `/etc/passwd`",
254
+ ].join("\n"),
255
+ }, "prose cannot close the section while a path still follows it");
256
+ });
257
+
258
+ test("whitespace-bearing surfaces require whole-entry backticks", async () => {
259
+ const backtickedSpaced = [
260
+ "## Allowed edit surfaces",
261
+ "- `Directory With Spaces/note.md`",
262
+ "`Directory\u00a0With\u2003Spaces/unicode.md`",
263
+ ];
264
+ const equivalentBacktickedSpaced = [
265
+ "## Allowed edit surfaces",
266
+ "`Directory With Spaces/note.md`",
267
+ "- `Directory\u00a0With\u2003Spaces/unicode.md`",
268
+ ];
269
+
270
+ await assertAccepted(
271
+ { agent: "gentle-ai-worker", mode: "task", task: backtickedSpaced.join("\n") },
272
+ "whole-entry backticked ASCII and Unicode space-separator paths are accepted",
273
+ );
274
+ await assertAccepted(
275
+ { agent: "gentle-ai-worker", mode: "task", task: backtickedSpaced.join("\n"), context: equivalentBacktickedSpaced.join("\n") },
276
+ "task and context compare equivalent backticked whitespace-bearing paths by value",
277
+ );
278
+ await assertAccepted({
279
+ agent: "gentle-ai-worker",
280
+ mode: "task",
281
+ task: [...backtickedSpaced, "### Validation", "node --test"].join("\n"),
282
+ }, "the next Markdown heading closes a backticked whitespace-bearing surface section");
283
+
284
+ for (const [label, entry] of [
285
+ ["bare spaced path", "Directory With Spaces/note.md"],
286
+ ["bare bulleted spaced path", "- Directory With Spaces/note.md"],
287
+ ["bare NBSP path", "Directory\u00a0With Spaces/note.md"],
288
+ ["bare em-space path", "Directory\u2003With Spaces/note.md"],
289
+ ["U+0085 control path", "`Directory\u0085With Spaces/note.md`"],
290
+ ["U+0085 list separator", "-\u0085`lib/sdd-status.ts`"],
291
+ ["U+2028 line-separator path", "`Directory\u2028With Spaces/note.md`"],
292
+ ["U+2029 paragraph-separator path", "`Directory\u2029With Spaces/note.md`"],
293
+ ["list-marked prose", "- Then run the focused test file."],
294
+ ["marker-only hyphen", "-"],
295
+ ["empty backticks", "``"],
296
+ ["unclosed backticks", "`lib/sdd-status.ts"],
297
+ ["partially backticked path", "`lib/sdd-status.ts` trailing"],
298
+ ["absolute spaced path", "/tmp/Outside Directory/file.md"],
299
+ ["parent spaced path", "../Other Directory/file.md"],
300
+ ["home spaced path", "~/Private Directory/file.md"],
301
+ ["Windows spaced path", "C:\\Outside Directory\\file.md"],
302
+ ["dangerous backticked bullet", "- `/tmp/Outside Directory/file.md`"],
303
+ ] as const) {
304
+ await assertRejected({
305
+ agent: "gentle-ai-worker",
306
+ mode: "task",
307
+ task: ["## Allowed edit surfaces", "lib/sdd-status.ts", entry].join("\n"),
308
+ }, `${label} after a valid entry is rejected`);
309
+ await assertRejected({
310
+ agent: "gentle-ai-worker",
311
+ mode: "task",
312
+ task: ["## Allowed edit surfaces", entry, "lib/sdd-status.ts"].join("\n"),
313
+ }, `${label} before a valid entry is rejected`);
314
+ }
315
+ });
316
+
317
+ test("out-of-scope and empty surfaces stay rejected", async () => {
318
+ await assertRejected({
319
+ agent: "gentle-ai-worker",
320
+ mode: "task",
321
+ task: "Fix the decoder.",
322
+ }, "a task with no section is rejected");
323
+ await assertRejected({
324
+ agent: "gentle-ai-worker",
325
+ mode: "task",
326
+ task: ["## Edit ranges", "- `lib/sdd-status.ts`"].join("\n"),
327
+ }, "a semantically equivalent heading is rejected with the actionable canonical-heading reason");
328
+ await assertRejected({
329
+ agent: "gentle-ai-worker",
330
+ mode: "task",
331
+ task: ["## Allowed edit surfaces", "", "## Skills to load before work", "- `skills/typescript/SKILL.md`"].join("\n"),
332
+ }, "an empty section is rejected");
333
+ await assertRejected({
334
+ agent: "gentle-ai-worker",
335
+ mode: "task",
336
+ task: ["## Allowed edit surfaces", "- `/home/user/repo/lib/sdd-status.ts`"].join("\n"),
337
+ }, "an absolute path is rejected");
338
+ await assertRejected({
339
+ agent: "gentle-ai-worker",
340
+ mode: "task",
341
+ task: ["## Allowed edit surfaces", "- `../other-repo/lib/sdd-status.ts`"].join("\n"),
342
+ }, "parent traversal is rejected");
343
+ await assertRejected({
344
+ agent: "gentle-ai-worker",
345
+ mode: "task",
346
+ task: ["## Allowed edit surfaces", "- `.`"].join("\n"),
347
+ }, "the repository root is rejected");
348
+ await assertRejected({
349
+ agent: "gentle-ai-worker",
350
+ mode: "task",
351
+ task: ["## Allowed edit surfaces", "- `*`"].join("\n"),
352
+ }, "a repository-wide glob is rejected");
353
+ await assertRejected({
354
+ agent: "worker",
355
+ mode: "task",
356
+ task: ["## Allowed edit surfaces", "- `~/lib/sdd-status.ts`"].join("\n"),
357
+ }, "a home-relative path is rejected for the configured worker too");
358
+ });
359
+
360
+ test("agents outside the bounded writer set are not scope-guarded", async () => {
361
+ await assertAccepted({
362
+ agent: "gentle-ai-explore",
363
+ mode: "task",
364
+ task: "Map the decoder call sites.",
365
+ }, "a read-only explorer needs no edit surfaces");
366
+ });
@@ -0,0 +1,94 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/earendil-works/pi-mono/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
3
+ "name": "Gentleman-Cute",
4
+ "vars": {
5
+ "bg": "#060407",
6
+ "bgPanel": "#100A0F",
7
+ "bgElement": "#100A0F",
8
+ "bgSubtle": "#100A0F",
9
+ "border": "#563040",
10
+ "borderSubtle": "#2A1720",
11
+ "text": "#F6EFF3",
12
+ "muted": "#A78E9B",
13
+ "dim": "#76616B",
14
+ "accent": "#F095C8",
15
+ "deepPink": "#C96AA2",
16
+ "activePink": "#FFB1DD",
17
+ "champagne": "#E0C27A",
18
+ "pearl": "#D2CBD0",
19
+ "softRose": "#D7A0B8",
20
+ "powderBlue": "#A9C7EE",
21
+ "mint": "#B4E7C7",
22
+ "peach": "#F2B86D",
23
+ "sky": "#C4DAF6",
24
+ "warning": "#F2B86D",
25
+ "error": "#FF718F",
26
+ "selection": "#28121E",
27
+ "toolPendingBg": "#100A0F",
28
+ "toolSuccessBg": "#151316",
29
+ "toolErrorBg": "#261019",
30
+ "infoBg": "#100A0F"
31
+ },
32
+ "colors": {
33
+ "accent": "accent",
34
+ "border": "border",
35
+ "borderAccent": "activePink",
36
+ "borderMuted": "borderSubtle",
37
+ "success": "mint",
38
+ "error": "error",
39
+ "warning": "warning",
40
+ "muted": "muted",
41
+ "dim": "dim",
42
+ "text": "text",
43
+ "thinkingText": "muted",
44
+ "selectedBg": "selection",
45
+ "scrollbarThumb": "border",
46
+ "searchMatchBg": "deepPink",
47
+ "searchMatchText": "text",
48
+ "userMessageBg": "bgElement",
49
+ "userMessageText": "text",
50
+ "customMessageBg": "bgSubtle",
51
+ "customMessageText": "text",
52
+ "customMessageLabel": "champagne",
53
+ "toolPendingBg": "toolPendingBg",
54
+ "toolSuccessBg": "toolSuccessBg",
55
+ "toolErrorBg": "toolErrorBg",
56
+ "toolTitle": "champagne",
57
+ "toolOutput": "text",
58
+ "mdHeading": "champagne",
59
+ "mdLink": "accent",
60
+ "mdLinkUrl": "muted",
61
+ "mdCode": "champagne",
62
+ "mdCodeBlock": "text",
63
+ "mdCodeBlockBorder": "muted",
64
+ "mdQuote": "muted",
65
+ "mdQuoteBorder": "deepPink",
66
+ "mdHr": "borderSubtle",
67
+ "mdListBullet": "accent",
68
+ "toolDiffAdded": "mint",
69
+ "toolDiffRemoved": "error",
70
+ "toolDiffContext": "muted",
71
+ "syntaxComment": "muted",
72
+ "syntaxKeyword": "accent",
73
+ "syntaxFunction": "powderBlue",
74
+ "syntaxVariable": "text",
75
+ "syntaxString": "mint",
76
+ "syntaxNumber": "peach",
77
+ "syntaxType": "champagne",
78
+ "syntaxOperator": "sky",
79
+ "syntaxPunctuation": "muted",
80
+ "thinkingOff": "borderSubtle",
81
+ "thinkingMinimal": "dim",
82
+ "thinkingLow": "muted",
83
+ "thinkingMedium": "powderBlue",
84
+ "thinkingHigh": "softRose",
85
+ "thinkingXhigh": "accent",
86
+ "thinkingMax": "activePink",
87
+ "bashMode": "champagne"
88
+ },
89
+ "export": {
90
+ "pageBg": "bg",
91
+ "cardBg": "bgElement",
92
+ "infoBg": "infoBg"
93
+ }
94
+ }
@@ -0,0 +1,92 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/earendil-works/pi-mono/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
3
+ "name": "Gentleman-Sexy",
4
+ "vars": {
5
+ "bg": "#060407",
6
+ "bgPanel": "#100A0F",
7
+ "bgElement": "#100A0F",
8
+ "bgSubtle": "#100A0F",
9
+ "border": "#563040",
10
+ "borderSubtle": "#2A1720",
11
+ "text": "#F6EFF3",
12
+ "muted": "#A78E9B",
13
+ "dim": "#76616B",
14
+ "accent": "#F43888",
15
+ "deepPink": "#BF0F50",
16
+ "activePink": "#FF4F9A",
17
+ "champagne": "#E0C27A",
18
+ "violet": "#C49BFF",
19
+ "pearl": "#D2CBD0",
20
+ "softRose": "#D7A0B8",
21
+ "powderBlue": "#A9C7EE",
22
+ "warning": "#F2B86D",
23
+ "error": "#FF718F",
24
+ "selection": "#28121E",
25
+ "toolPendingBg": "#100A0F",
26
+ "toolSuccessBg": "#151316",
27
+ "toolErrorBg": "#261019",
28
+ "infoBg": "#100A0F"
29
+ },
30
+ "colors": {
31
+ "accent": "accent",
32
+ "border": "border",
33
+ "borderAccent": "activePink",
34
+ "borderMuted": "borderSubtle",
35
+ "success": "pearl",
36
+ "error": "error",
37
+ "warning": "warning",
38
+ "muted": "muted",
39
+ "dim": "dim",
40
+ "text": "text",
41
+ "thinkingText": "muted",
42
+ "selectedBg": "selection",
43
+ "scrollbarThumb": "border",
44
+ "searchMatchBg": "deepPink",
45
+ "searchMatchText": "text",
46
+ "userMessageBg": "bgElement",
47
+ "userMessageText": "text",
48
+ "customMessageBg": "bgSubtle",
49
+ "customMessageText": "text",
50
+ "customMessageLabel": "champagne",
51
+ "toolPendingBg": "toolPendingBg",
52
+ "toolSuccessBg": "toolSuccessBg",
53
+ "toolErrorBg": "toolErrorBg",
54
+ "toolTitle": "champagne",
55
+ "toolOutput": "text",
56
+ "mdHeading": "champagne",
57
+ "mdLink": "activePink",
58
+ "mdLinkUrl": "muted",
59
+ "mdCode": "champagne",
60
+ "mdCodeBlock": "text",
61
+ "mdCodeBlockBorder": "muted",
62
+ "mdQuote": "muted",
63
+ "mdQuoteBorder": "deepPink",
64
+ "mdHr": "borderSubtle",
65
+ "mdListBullet": "activePink",
66
+ "toolDiffAdded": "powderBlue",
67
+ "toolDiffRemoved": "error",
68
+ "toolDiffContext": "muted",
69
+ "syntaxComment": "muted",
70
+ "syntaxKeyword": "deepPink",
71
+ "syntaxFunction": "violet",
72
+ "syntaxVariable": "text",
73
+ "syntaxString": "champagne",
74
+ "syntaxNumber": "softRose",
75
+ "syntaxType": "powderBlue",
76
+ "syntaxOperator": "activePink",
77
+ "syntaxPunctuation": "muted",
78
+ "thinkingOff": "borderSubtle",
79
+ "thinkingMinimal": "dim",
80
+ "thinkingLow": "muted",
81
+ "thinkingMedium": "powderBlue",
82
+ "thinkingHigh": "violet",
83
+ "thinkingXhigh": "activePink",
84
+ "thinkingMax": "accent",
85
+ "bashMode": "champagne"
86
+ },
87
+ "export": {
88
+ "pageBg": "bg",
89
+ "cardBg": "bgElement",
90
+ "infoBg": "infoBg"
91
+ }
92
+ }