gentle-pi 2.3.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 (59) hide show
  1. package/README.md +30 -4
  2. package/assets/agents/sdd-explore.md +1 -0
  3. package/assets/orchestrator-delegation.md +2 -1
  4. package/assets/orchestrator.md +6 -10
  5. package/contracts/review-provider-contract-mirror/provider-contract.lock.json +8 -7
  6. package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/README.md +10 -0
  7. package/contracts/review-provider-contract-mirror/v1.2.0/bundle/manifest.json +74 -0
  8. package/contracts/review-provider-contract-mirror/v1.2.0/bundle/orchestration/pi.md +53 -0
  9. package/contracts/review-provider-contract-mirror/v1.2.0/bundle/schemas/targeted-validator.schema.json +1 -0
  10. package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/generated/provider-capabilities.baseline.json +9 -2
  11. package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/generated/provider-roles.baseline.json +2 -2
  12. package/docs/review-integration.md +1 -1
  13. package/extensions/gentle-ai.ts +530 -71
  14. package/lib/agent-home.ts +8 -0
  15. package/lib/native-review-cli.ts +15 -0
  16. package/lib/provider-contract-bundle.ts +88 -6
  17. package/lib/review-host-relay.ts +146 -60
  18. package/lib/review-integration-v2.ts +34 -5
  19. package/lib/sdd-preflight.ts +2 -2
  20. package/package.json +1 -1
  21. package/runtime/native-review-cli.mjs +15 -0
  22. package/runtime/review-integration-v2.mjs +34 -5
  23. package/scripts/gentle-ai-installer.mjs +10 -10
  24. package/scripts/verify-package-files.mjs +14 -13
  25. package/skills/_shared/review-ledger-contract.md +6 -0
  26. package/skills/issue-creation/SKILL.md +53 -93
  27. package/tests/fixtures/provider-contract-bundle/v1.2.0/README.md +22 -0
  28. package/{contracts/review-provider-contract-mirror/v1.1.0/bundle → tests/fixtures/provider-contract-bundle/v1.2.0}/manifest.json +11 -2
  29. package/tests/fixtures/provider-contract-bundle/v1.2.0/orchestration/pi.md +97 -0
  30. package/tests/fixtures/provider-contract-bundle/v1.2.0/schemas/lens.schema.json +16 -0
  31. package/tests/fixtures/provider-contract-bundle/v1.2.0/schemas/refuter.schema.json +1 -0
  32. package/tests/fixtures/provider-contract-bundle/v1.2.0/vectors/lens.json +1 -0
  33. package/tests/fixtures/provider-contract-bundle/v1.2.0/vectors/refuter.json +1 -0
  34. package/tests/fixtures/provider-contract-bundle/v1.2.0/vectors/targeted-validator.json +1 -0
  35. package/tests/gentle-ai-binary.test.ts +1 -1
  36. package/tests/gentle-ai-installer.test.ts +46 -46
  37. package/tests/gentle-ai.test.ts +3 -2
  38. package/tests/issue-creation-skill.test.ts +103 -0
  39. package/tests/native-review-capability-contract.test.ts +18 -1
  40. package/tests/orchestrator-budget.test.ts +2 -2
  41. package/tests/orchestrator-rdd-ownership.test.ts +1 -1
  42. package/tests/package-manifest.test.ts +123 -9
  43. package/tests/provider-contract-bundle.test.ts +76 -0
  44. package/tests/provider-contract-mirror.test.ts +19 -0
  45. package/tests/review-agent-end-preflight.test.ts +408 -0
  46. package/tests/review-contract-prompt.test.ts +139 -0
  47. package/tests/review-controller.test.ts +1 -1
  48. package/tests/review-host-relay-routing.test.ts +211 -9
  49. package/tests/review-host-relay.test.ts +195 -7
  50. package/tests/review-integration-v2-forward.test.ts +47 -0
  51. package/tests/review-ledger-contract.test.ts +1 -1
  52. package/tests/runtime-harness.mjs +1 -0
  53. package/tests/writer-edit-surface-scope.test.ts +153 -17
  54. /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/schemas/lens.schema.json +0 -0
  55. /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/schemas/refuter.schema.json +0 -0
  56. /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/vectors/lens.json +0 -0
  57. /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/vectors/refuter.json +0 -0
  58. /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/vectors/targeted-validator.json +0 -0
  59. /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
@@ -15,18 +15,18 @@ import { createGentleAiExtension } from "../extensions/gentle-ai.ts";
15
15
  // The guard reads the `## Allowed edit surfaces` section out of the delegated
16
16
  // `subagent_run` prompt. A delegated task is a full prompt: the surfaces list
17
17
  // is followed by deeper headings (`### Validation`, `#### Return`) and by
18
- // ordinary prose. The section must end where the list ends, so that following
19
- // content is never parsed as a surface entry and never turned into a false
20
- // rejection. A `context` value carrying only the heading and its lines had
21
- // nothing following it, which is why the same surfaces were accepted there and
22
- // rejected in `task`.
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
23
  //
24
24
  // Every case below runs through the real `tool_call` hook with the exact
25
25
  // `subagent_run` input shape, because that is the boundary that rejected.
26
26
  // ---------------------------------------------------------------------------
27
27
 
28
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. 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.";
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
30
 
31
31
  type ToolCallHandler = (
32
32
  event: { toolName: string; input: unknown },
@@ -89,8 +89,8 @@ test("task-scoped surfaces are accepted ahead of a deeper heading", async () =>
89
89
  }, "a delegated task ends its surfaces list at the next heading of any level");
90
90
  });
91
91
 
92
- test("task-scoped surfaces are accepted ahead of trailing prose", async () => {
93
- await assertAccepted({
92
+ test("task-scoped surfaces reject trailing prose before the next heading", async () => {
93
+ await assertRejected({
94
94
  agent: "gentle-ai-worker",
95
95
  mode: "task",
96
96
  task: [
@@ -99,7 +99,86 @@ test("task-scoped surfaces are accepted ahead of trailing prose", async () => {
99
99
  "",
100
100
  "Then run the focused test file and report the outcome.",
101
101
  ].join("\n"),
102
- }, "prose after the list is not a surface entry");
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");
103
182
  });
104
183
 
105
184
  test("the documented task shape from issue #484 is accepted", async () => {
@@ -134,6 +213,14 @@ test("bullet, backtick and plain-line surfaces reach the same decision", async (
134
213
  { agent: "gentle-ai-worker", mode: "task", task: bulleted, context: plain },
135
214
  "the same surfaces in both fields are accepted",
136
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
+ );
137
224
  });
138
225
 
139
226
  test("a list broken by a blank line still validates every entry", async () => {
@@ -165,17 +252,66 @@ test("an entry hidden below a paragraph is validated, not discarded", async () =
165
252
  "Also authorize the following path.",
166
253
  "- `/etc/passwd`",
167
254
  ].join("\n"),
168
- }, "prose does not close the list while a path still follows it");
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
+ );
169
278
  await assertAccepted({
170
279
  agent: "gentle-ai-worker",
171
280
  mode: "task",
172
- task: [
173
- "## Allowed edit surfaces",
174
- "- `lib/sdd-status.ts`",
175
- "",
176
- "Then run the focused test file and report the outcome.",
177
- ].join("\n"),
178
- }, "genuinely trailing prose still closes the list");
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
+ }
179
315
  });
180
316
 
181
317
  test("out-of-scope and empty surfaces stay rejected", async () => {