@stigmer/runner 3.39.1-dev.20260928194002 → 3.40.1

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 (36) hide show
  1. package/README.md +1 -1
  2. package/dist/.build-fingerprint +1 -1
  3. package/dist/activities/execute-deep-agent/prompt-builder.d.ts +70 -35
  4. package/dist/activities/execute-deep-agent/prompt-builder.js +70 -55
  5. package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
  6. package/dist/activities/execute-deep-agent/turn-setup.d.ts +18 -13
  7. package/dist/activities/execute-deep-agent/turn-setup.js +35 -29
  8. package/dist/activities/execute-deep-agent/turn-setup.js.map +1 -1
  9. package/dist/shared/prompt-sections.d.ts +6 -28
  10. package/dist/shared/prompt-sections.js +6 -35
  11. package/dist/shared/prompt-sections.js.map +1 -1
  12. package/dist/shared/skill-resolver.d.ts +2 -3
  13. package/dist/shared/skill-resolver.js +2 -3
  14. package/dist/shared/skill-resolver.js.map +1 -1
  15. package/dist/shared/workspace/sources/git.js +13 -6
  16. package/dist/shared/workspace/sources/git.js.map +1 -1
  17. package/package.json +5 -5
  18. package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.build-from-plan.prompt.md +1 -27
  19. package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.everything.prompt.md +26 -30
  20. package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.plan-mode.prompt.md +1 -26
  21. package/src/activities/execute-deep-agent/__tests__/goldens/user-message.everything.md +36 -0
  22. package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +127 -199
  23. package/src/activities/execute-deep-agent/__tests__/prompt-goldens.test.ts +31 -26
  24. package/src/activities/execute-deep-agent/prompt-builder.ts +104 -92
  25. package/src/activities/execute-deep-agent/turn-setup.ts +36 -31
  26. package/src/shared/__tests__/prompt-sections.test.ts +2 -54
  27. package/src/shared/prompt-sections.ts +6 -55
  28. package/src/shared/skill-resolver.ts +2 -3
  29. package/src/shared/workspace/__tests__/git-source.test.ts +11 -5
  30. package/src/shared/workspace/sources/git.ts +14 -8
  31. package/dist/shared/skill-relevance.d.ts +0 -65
  32. package/dist/shared/skill-relevance.js +0 -175
  33. package/dist/shared/skill-relevance.js.map +0 -1
  34. package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.skills-below-threshold.prompt.md +0 -146
  35. package/src/shared/__tests__/skill-relevance.test.ts +0 -292
  36. package/src/shared/skill-relevance.ts +0 -222
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect } from "vitest";
2
2
  import { InteractionMode } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
3
- import { buildEnhancedSystemPrompt, composeUserMessage } from "../prompt-builder.js";
3
+ import { buildEnhancedSystemPrompt, composeUserMessage, type TurnMessageInput } from "../prompt-builder.js";
4
4
  import { PLAN_MODE_DIRECTIVE } from "../../../shared/plan-mode-prompt.js";
5
5
  import type { ProvisionResult } from "../../../shared/workspace/types.js";
6
6
 
@@ -22,9 +22,6 @@ describe("buildEnhancedSystemPrompt", () => {
22
22
  provisionResults: [],
23
23
  containerRoot: "",
24
24
  skillsPromptSection: "",
25
- workspaceFileRefs: [],
26
- workspaceRoot: "/workspace",
27
- inputFiles: [],
28
25
  });
29
26
 
30
27
  expect(prompt.startsWith("You are a helpful assistant.")).toBe(true);
@@ -36,9 +33,6 @@ describe("buildEnhancedSystemPrompt", () => {
36
33
  provisionResults: [],
37
34
  containerRoot: "",
38
35
  skillsPromptSection: "",
39
- workspaceFileRefs: [],
40
- workspaceRoot: "/workspace",
41
- inputFiles: [],
42
36
  });
43
37
 
44
38
  expect(prompt).toContain("## Response rules");
@@ -50,9 +44,6 @@ describe("buildEnhancedSystemPrompt", () => {
50
44
  provisionResults: [],
51
45
  containerRoot: "",
52
46
  skillsPromptSection: "",
53
- workspaceFileRefs: [],
54
- workspaceRoot: "/workspace",
55
- inputFiles: [],
56
47
  });
57
48
 
58
49
  expect(prompt).toContain("## Working with tools");
@@ -66,9 +57,6 @@ describe("buildEnhancedSystemPrompt", () => {
66
57
  provisionResults: [],
67
58
  containerRoot: "",
68
59
  skillsPromptSection: "",
69
- workspaceFileRefs: [],
70
- workspaceRoot: "/workspace",
71
- inputFiles: [],
72
60
  interactionMode: InteractionMode.PLAN,
73
61
  });
74
62
 
@@ -82,9 +70,6 @@ describe("buildEnhancedSystemPrompt", () => {
82
70
  provisionResults: [makeProvisionResult()],
83
71
  containerRoot: "/workspace",
84
72
  skillsPromptSection: "",
85
- workspaceFileRefs: [],
86
- workspaceRoot: "/workspace",
87
- inputFiles: [],
88
73
  });
89
74
 
90
75
  expect(prompt).toContain("## Workspace");
@@ -100,9 +85,6 @@ describe("buildEnhancedSystemPrompt", () => {
100
85
  ],
101
86
  containerRoot: "/workspace",
102
87
  skillsPromptSection: "",
103
- workspaceFileRefs: [],
104
- workspaceRoot: "/workspace",
105
- inputFiles: [],
106
88
  });
107
89
 
108
90
  expect(prompt).toContain("2 workspace entries");
@@ -118,9 +100,6 @@ describe("buildEnhancedSystemPrompt", () => {
118
100
  })],
119
101
  containerRoot: "/workspace",
120
102
  skillsPromptSection: "",
121
- workspaceFileRefs: [],
122
- workspaceRoot: "/workspace",
123
- inputFiles: [],
124
103
  });
125
104
 
126
105
  expect(prompt).toContain("src/");
@@ -133,151 +112,12 @@ describe("buildEnhancedSystemPrompt", () => {
133
112
  provisionResults: [],
134
113
  containerRoot: "",
135
114
  skillsPromptSection: "\n\n## Skills\n\n- coding-standards",
136
- workspaceFileRefs: [],
137
- workspaceRoot: "/workspace",
138
- inputFiles: [],
139
115
  });
140
116
 
141
117
  expect(prompt).toContain("## Skills");
142
118
  expect(prompt).toContain("coding-standards");
143
119
  });
144
120
 
145
- it("includes referenced files section", () => {
146
- const prompt = buildEnhancedSystemPrompt({
147
- instructions: "Test",
148
- provisionResults: [],
149
- containerRoot: "",
150
- skillsPromptSection: "",
151
- workspaceFileRefs: ["src/config.yaml", "README.md"],
152
- workspaceRoot: "/workspace",
153
- inputFiles: [],
154
- });
155
-
156
- expect(prompt).toContain("## Referenced Files");
157
- expect(prompt).toContain("`src/config.yaml`");
158
- expect(prompt).toContain("`README.md`");
159
- });
160
-
161
- it("includes injected files section with size info", () => {
162
- // The section renders the injector's own result type (sizeBytes). A local
163
- // structural twin with a `size` field used to live in the prompt builder,
164
- // and production wiring silently dropped the size — this pin holds the
165
- // real field name so that drift class cannot return.
166
- const prompt = buildEnhancedSystemPrompt({
167
- instructions: "Test",
168
- provisionResults: [],
169
- containerRoot: "",
170
- skillsPromptSection: "",
171
- workspaceFileRefs: [],
172
- workspaceRoot: "/workspace",
173
- inputFiles: [
174
- { filename: "data.csv", relativePath: ".stigmer/inputs/data.csv", sizeBytes: 1024 },
175
- { filename: "notes.md", relativePath: ".stigmer/inputs/notes.md", sizeBytes: 12 },
176
- ],
177
- });
178
-
179
- expect(prompt).toContain("## Input Files");
180
- expect(prompt).toContain("`.stigmer/inputs/data.csv` (1024 bytes)");
181
- expect(prompt).toContain("`.stigmer/inputs/notes.md` (12 bytes)");
182
- });
183
-
184
- it("discloses a duplicate-renamed injected file's original name", () => {
185
- const prompt = buildEnhancedSystemPrompt({
186
- instructions: "Test",
187
- provisionResults: [],
188
- containerRoot: "",
189
- skillsPromptSection: "",
190
- workspaceFileRefs: [],
191
- workspaceRoot: "/workspace",
192
- inputFiles: [
193
- { filename: "report.pdf", relativePath: ".stigmer/inputs/report.pdf", sizeBytes: 10 },
194
- {
195
- filename: "report-2.pdf",
196
- relativePath: ".stigmer/inputs/report-2.pdf",
197
- sizeBytes: 20,
198
- renamedFrom: "report.pdf",
199
- },
200
- ],
201
- });
202
-
203
- expect(prompt).toContain(
204
- "`.stigmer/inputs/report-2.pdf` (20 bytes) (renamed from duplicate 'report.pdf')",
205
- );
206
- // The first file keeps a clean entry — no disclosure noise.
207
- expect(prompt).toContain("`.stigmer/inputs/report.pdf` (10 bytes)\n");
208
- });
209
-
210
- it("lists a minted download URL beside its file with the presigned hand-off line (issue #532)", () => {
211
- const prompt = buildEnhancedSystemPrompt({
212
- instructions: "Test",
213
- provisionResults: [],
214
- containerRoot: "",
215
- skillsPromptSection: "",
216
- workspaceFileRefs: [],
217
- workspaceRoot: "/workspace",
218
- inputFiles: [
219
- {
220
- filename: "lease.pdf",
221
- relativePath: ".stigmer/inputs/lease.pdf",
222
- sizeBytes: 2048,
223
- downloadUrl: "https://r2.example/lease?sig=abc",
224
- },
225
- { filename: "notes.md", relativePath: ".stigmer/inputs/notes.md", sizeBytes: 12 },
226
- ],
227
- downloadUrlKind: "presigned",
228
- });
229
-
230
- expect(prompt).toContain(
231
- "`.stigmer/inputs/lease.pdf` (2048 bytes) — download URL: https://r2.example/lease?sig=abc",
232
- );
233
- // The URL-less file keeps a clean entry.
234
- expect(prompt).toContain("`.stigmer/inputs/notes.md` (12 bytes)\n");
235
- expect(prompt).toContain("These URLs are time-limited");
236
- });
237
-
238
- it("words a local-serve URL honestly — reachable only from this machine", () => {
239
- const prompt = buildEnhancedSystemPrompt({
240
- instructions: "Test",
241
- provisionResults: [],
242
- containerRoot: "",
243
- skillsPromptSection: "",
244
- workspaceFileRefs: [],
245
- workspaceRoot: "/workspace",
246
- inputFiles: [
247
- {
248
- filename: "lease.pdf",
249
- relativePath: ".stigmer/inputs/lease.pdf",
250
- sizeBytes: 2048,
251
- downloadUrl: "http://localhost:7235/attachments/01A/lease.pdf",
252
- },
253
- ],
254
- downloadUrlKind: "local-serve",
255
- });
256
-
257
- expect(prompt).toContain("download URL: http://localhost:7235/attachments/01A/lease.pdf");
258
- expect(prompt).toContain("reachable only from this machine");
259
- expect(prompt).not.toContain("time-limited");
260
- });
261
-
262
- it("renders no hand-off line when no listed file carries a URL (kind alone is not enough)", () => {
263
- const prompt = buildEnhancedSystemPrompt({
264
- instructions: "Test",
265
- provisionResults: [],
266
- containerRoot: "",
267
- skillsPromptSection: "",
268
- workspaceFileRefs: [],
269
- workspaceRoot: "/workspace",
270
- inputFiles: [
271
- { filename: "local.csv", relativePath: ".stigmer/inputs/local.csv", sizeBytes: 5 },
272
- ],
273
- downloadUrlKind: "presigned",
274
- });
275
-
276
- expect(prompt).toContain("## Input Files");
277
- expect(prompt).not.toContain("download URL");
278
- expect(prompt).not.toContain("time-limited");
279
- });
280
-
281
121
  it("produces a git repo description for git workspace entries", () => {
282
122
  const prompt = buildEnhancedSystemPrompt({
283
123
  instructions: "Test",
@@ -301,13 +141,10 @@ describe("buildEnhancedSystemPrompt", () => {
301
141
  ],
302
142
  containerRoot: "/workspace",
303
143
  skillsPromptSection: "",
304
- workspaceFileRefs: [],
305
- workspaceRoot: "/workspace",
306
- inputFiles: [],
307
144
  });
308
145
 
309
- expect(prompt).toContain("https://github.com/org/my-repo");
310
- expect(prompt).toContain("abc1234");
146
+ expect(prompt).toContain("https://github.com/org/my-repo (branch: main)");
147
+ expect(prompt, "no commit: the clone's HEAD moves with every write-back commit").not.toContain("abc1234");
311
148
  expect(prompt).toContain("empty workspace");
312
149
  });
313
150
 
@@ -317,9 +154,6 @@ describe("buildEnhancedSystemPrompt", () => {
317
154
  provisionResults: [],
318
155
  containerRoot: "",
319
156
  skillsPromptSection: "",
320
- workspaceFileRefs: [],
321
- workspaceRoot: "/workspace",
322
- inputFiles: [],
323
157
  });
324
158
 
325
159
  expect(prompt).not.toContain("## Workspace");
@@ -331,9 +165,7 @@ describe("buildEnhancedSystemPrompt", () => {
331
165
  provisionResults: [],
332
166
  containerRoot: "",
333
167
  skillsPromptSection: "",
334
- workspaceFileRefs: [],
335
168
  workspaceRoot: "",
336
- inputFiles: [],
337
169
  };
338
170
 
339
171
  it("appends the bridge as standing session context (every-turn injection)", () => {
@@ -360,9 +192,7 @@ describe("buildEnhancedSystemPrompt", () => {
360
192
  provisionResults: [],
361
193
  containerRoot: "",
362
194
  skillsPromptSection: "",
363
- workspaceFileRefs: [],
364
195
  workspaceRoot: "",
365
- inputFiles: [],
366
196
  };
367
197
 
368
198
  it("appends the sender as standing session context (every-turn injection)", () => {
@@ -389,9 +219,7 @@ describe("buildEnhancedSystemPrompt", () => {
389
219
  provisionResults: [],
390
220
  containerRoot: "",
391
221
  skillsPromptSection: "",
392
- workspaceFileRefs: [],
393
222
  workspaceRoot: "",
394
- inputFiles: [],
395
223
  };
396
224
 
397
225
  it("appends the context as standing session context (every-turn injection)", () => {
@@ -434,9 +262,7 @@ describe("buildEnhancedSystemPrompt", () => {
434
262
  provisionResults: [],
435
263
  containerRoot: "",
436
264
  skillsPromptSection: "",
437
- workspaceFileRefs: [],
438
265
  workspaceRoot: "",
439
- inputFiles: [],
440
266
  };
441
267
 
442
268
  it("appends the preferences with per-scope attribution (every-turn injection)", () => {
@@ -486,9 +312,7 @@ describe("buildEnhancedSystemPrompt", () => {
486
312
  provisionResults: [],
487
313
  containerRoot: "",
488
314
  skillsPromptSection: "",
489
- workspaceFileRefs: [],
490
315
  workspaceRoot: "",
491
- inputFiles: [],
492
316
  };
493
317
 
494
318
  it("appends the confirmed facts with the defensive framing (every-turn injection)", () => {
@@ -534,9 +358,6 @@ describe("buildEnhancedSystemPrompt", () => {
534
358
  provisionResults: [],
535
359
  containerRoot: "",
536
360
  skillsPromptSection: "",
537
- workspaceFileRefs: [],
538
- workspaceRoot: "/workspace",
539
- inputFiles: [],
540
361
  };
541
362
 
542
363
  it("appends the shared plan-mode directive plus the native-only read-boundary line as the final section", () => {
@@ -576,21 +397,14 @@ describe("buildEnhancedSystemPrompt", () => {
576
397
  provisionResults: [],
577
398
  containerRoot: "",
578
399
  skillsPromptSection: "",
579
- workspaceFileRefs: [],
580
- workspaceRoot: "/workspace",
581
- inputFiles: [],
582
- };
583
- const planFile = {
584
- filename: "plan.md",
585
- relativePath: ".stigmer/inputs/plan.md",
586
- sizeBytes: 1024,
587
400
  };
401
+ const planPath = ".stigmer/inputs/plan.md";
588
402
 
589
403
  it("appends the implement-plan directive pointing at the injected plan", () => {
590
404
  const prompt = buildEnhancedSystemPrompt({
591
405
  ...base,
592
406
  buildFromPlan: true,
593
- inputFiles: [planFile],
407
+ approvedPlanPath: planPath,
594
408
  });
595
409
 
596
410
  expect(prompt).toContain("## Implement the approved plan");
@@ -612,7 +426,7 @@ describe("buildEnhancedSystemPrompt", () => {
612
426
  it("omits the directive for an ordinary execution", () => {
613
427
  const prompt = buildEnhancedSystemPrompt({
614
428
  ...base,
615
- inputFiles: [planFile],
429
+ approvedPlanPath: planPath,
616
430
  });
617
431
 
618
432
  expect(prompt).not.toContain("## Implement the approved plan");
@@ -622,7 +436,7 @@ describe("buildEnhancedSystemPrompt", () => {
622
436
  const prompt = buildEnhancedSystemPrompt({
623
437
  ...base,
624
438
  buildFromPlan: true,
625
- inputFiles: [planFile],
439
+ approvedPlanPath: planPath,
626
440
  });
627
441
 
628
442
  expect(prompt).toContain("to-do list");
@@ -635,9 +449,11 @@ describe("composeUserMessage (conversation catchup, cloud DD-006 / A27)", () =>
635
449
  const MESSAGE = "where is my order?";
636
450
  const DIGEST =
637
451
  "Customer: I want a refund\nTeammate: I've refunded you in full.";
452
+ const withCatchup = (conversationCatchup: string | undefined): string =>
453
+ composeUserMessage({ message: MESSAGE, inputFiles: [], workspaceFileRefs: [], ...(conversationCatchup !== undefined ? { conversationCatchup } : {}) });
638
454
 
639
455
  it("prepends the framed catchup to the turn's user message — history durability rides the checkpointer, not the rebuilt system prompt", () => {
640
- const composed = composeUserMessage(MESSAGE, DIGEST);
456
+ const composed = withCatchup(DIGEST);
641
457
 
642
458
  expect(composed.endsWith(MESSAGE)).toBe(true);
643
459
  expect(composed).toContain(DIGEST);
@@ -646,11 +462,11 @@ describe("composeUserMessage (conversation catchup, cloud DD-006 / A27)", () =>
646
462
  });
647
463
 
648
464
  it("separates the catchup from the customer's message with a horizontal rule", () => {
649
- expect(composeUserMessage(MESSAGE, DIGEST)).toContain("\n\n---\n\n");
465
+ expect(withCatchup(DIGEST)).toContain("\n\n---\n\n");
650
466
  });
651
467
 
652
468
  it("leaves the message untouched when there is no catchup — most turns carry none", () => {
653
- expect(composeUserMessage(MESSAGE, undefined)).toBe(MESSAGE);
469
+ expect(withCatchup(undefined)).toBe(MESSAGE);
654
470
  });
655
471
 
656
472
  it("never renders in the system prompt — the rebuilt-per-invocation lane would forget the digest one turn later", () => {
@@ -659,12 +475,124 @@ describe("composeUserMessage (conversation catchup, cloud DD-006 / A27)", () =>
659
475
  provisionResults: [],
660
476
  containerRoot: "",
661
477
  skillsPromptSection: "",
662
- workspaceFileRefs: [],
663
- workspaceRoot: "/workspace",
664
- inputFiles: [],
665
478
  });
666
479
 
667
480
  expect(prompt).not.toContain("Conversation catchup");
668
481
  expect(prompt).not.toContain("you have not seen");
669
482
  });
670
483
  });
484
+
485
+ describe("composeUserMessage (the turn's payload rides the turn's message)", () => {
486
+ const MESSAGE = "Review the attached report.";
487
+ const message = (overrides: Partial<TurnMessageInput>): string =>
488
+ composeUserMessage({ message: MESSAGE, inputFiles: [], workspaceFileRefs: [], ...overrides });
489
+
490
+ it("names the referenced workspace paths, one bullet each, before the message", () => {
491
+ const composed = message({ workspaceFileRefs: ["src/config.yaml", "README.md"] });
492
+
493
+ expect(composed).toContain("## Referenced Files");
494
+ expect(composed).toContain("- `src/config.yaml`\n- `README.md`");
495
+ expect(composed.endsWith(`\n\n---\n\n${MESSAGE}`)).toBe(true);
496
+ });
497
+
498
+ it("lists the input files with their size", () => {
499
+ // The section renders the injector's own result type (sizeBytes). A local
500
+ // structural twin with a `size` field used to live in the prompt builder,
501
+ // and production wiring silently dropped the size — this pin holds the
502
+ // real field name so that drift class cannot return.
503
+ const composed = message({
504
+ inputFiles: [
505
+ { filename: "data.csv", relativePath: ".stigmer/inputs/data.csv", sizeBytes: 1024 },
506
+ { filename: "notes.md", relativePath: ".stigmer/inputs/notes.md", sizeBytes: 12 },
507
+ ],
508
+ });
509
+
510
+ expect(composed).toContain("## Input Files");
511
+ expect(composed).toContain("`.stigmer/inputs/data.csv` (1024 bytes)");
512
+ expect(composed).toContain("`.stigmer/inputs/notes.md` (12 bytes)");
513
+ });
514
+
515
+ it("discloses a duplicate-renamed input file's original name", () => {
516
+ const composed = message({
517
+ inputFiles: [
518
+ { filename: "report.pdf", relativePath: ".stigmer/inputs/report.pdf", sizeBytes: 10 },
519
+ { filename: "report-2.pdf", relativePath: ".stigmer/inputs/report-2.pdf", sizeBytes: 20, renamedFrom: "report.pdf" },
520
+ ],
521
+ });
522
+
523
+ expect(composed).toContain("`.stigmer/inputs/report-2.pdf` (20 bytes) (renamed from duplicate 'report.pdf')");
524
+ // The first file keeps a clean entry — no disclosure noise.
525
+ expect(composed).toContain("`.stigmer/inputs/report.pdf` (10 bytes)\n");
526
+ });
527
+
528
+ it("lists a minted download URL beside its file with the presigned hand-off line (issue #532)", () => {
529
+ const composed = message({
530
+ inputFiles: [
531
+ { filename: "lease.pdf", relativePath: ".stigmer/inputs/lease.pdf", sizeBytes: 2048, downloadUrl: "https://r2.example/lease?sig=abc" },
532
+ { filename: "notes.md", relativePath: ".stigmer/inputs/notes.md", sizeBytes: 12 },
533
+ ],
534
+ downloadUrlKind: "presigned",
535
+ });
536
+
537
+ expect(composed).toContain("`.stigmer/inputs/lease.pdf` (2048 bytes) — download URL: https://r2.example/lease?sig=abc");
538
+ // The URL-less file keeps a clean entry.
539
+ expect(composed).toContain("`.stigmer/inputs/notes.md` (12 bytes)\n");
540
+ expect(composed).toContain("These URLs are time-limited");
541
+ });
542
+
543
+ it("words a local-serve URL honestly — reachable only from this machine", () => {
544
+ const composed = message({
545
+ inputFiles: [
546
+ {
547
+ filename: "lease.pdf",
548
+ relativePath: ".stigmer/inputs/lease.pdf",
549
+ sizeBytes: 2048,
550
+ downloadUrl: "http://localhost:7235/attachments/01A/lease.pdf",
551
+ },
552
+ ],
553
+ downloadUrlKind: "local-serve",
554
+ });
555
+
556
+ expect(composed).toContain("download URL: http://localhost:7235/attachments/01A/lease.pdf");
557
+ expect(composed).toContain("reachable only from this machine");
558
+ expect(composed).not.toContain("time-limited");
559
+ });
560
+
561
+ it("renders no hand-off line when no listed file carries a URL (kind alone is not enough)", () => {
562
+ const composed = message({
563
+ inputFiles: [{ filename: "local.csv", relativePath: ".stigmer/inputs/local.csv", sizeBytes: 5 }],
564
+ downloadUrlKind: "presigned",
565
+ });
566
+
567
+ expect(composed).toContain("## Input Files");
568
+ expect(composed).not.toContain("download URL");
569
+ expect(composed).not.toContain("time-limited");
570
+ });
571
+
572
+ it("orders the payload first, the catchup next and what the user typed last, each set off by a rule", () => {
573
+ const composed = message({
574
+ inputFiles: [{ filename: "a.txt", relativePath: ".stigmer/inputs/a.txt", sizeBytes: 1 }],
575
+ workspaceFileRefs: ["src/a.ts"],
576
+ conversationCatchup: "Customer: hello",
577
+ });
578
+ const parts = composed.split("\n\n---\n\n");
579
+
580
+ expect(parts).toHaveLength(4);
581
+ expect(parts[0]).toMatch(/^## Input Files/);
582
+ expect(parts[1]).toMatch(/^## Referenced Files/);
583
+ expect(parts[2]).toContain("Customer: hello");
584
+ expect(parts[3]).toBe(MESSAGE);
585
+ });
586
+
587
+ it("never renders the payload in the system prompt, which is the session's", () => {
588
+ const prompt = buildEnhancedSystemPrompt({
589
+ instructions: "Test",
590
+ provisionResults: [],
591
+ containerRoot: "",
592
+ skillsPromptSection: "",
593
+ });
594
+
595
+ expect(prompt).not.toContain("## Input Files");
596
+ expect(prompt).not.toContain("## Referenced Files");
597
+ });
598
+ });
@@ -1,11 +1,10 @@
1
1
  /**
2
- * The native system prompt, whole, as goldens.
2
+ * The native system prompt and the turn's user message, whole, as goldens.
3
3
  *
4
4
  * Every other prompt test in this directory asserts with `toContain`; the
5
5
  * hermetic goldens are status JSON and never see a prompt. So until #1096 no
6
- * test pinned a rendered prompt byte for byte, and the relevance-filtered
7
- * `## Skills` section (eight or more skills, `turn-setup.ts`
8
- * `renderRootSkillsSection`) had no test at all. These goldens are the
6
+ * test pinned a rendered prompt byte for byte, and the `## Skills` section at
7
+ * eight or more skills had no test at all. These goldens are the
9
8
  * photograph taken BEFORE the shared prompt glue moved into
10
9
  * `shared/prompt-sections.ts` (#1096), so the move could be proven
11
10
  * byte-identical — the same discipline the hermetic net applied to the
@@ -16,17 +15,17 @@
16
15
  * `systemPromptOf`), so a changed byte can silently re-route a scripted reply
17
16
  * in the sub-agent delegation golden.
18
17
  *
19
- * The shapes: everything at once (the multi-entry workspace, nine skills so
20
- * the filter fires, channel templates, referenced files, three input files
21
- * with a rename and a download URL, the vision disclosure, all five standing
22
- * sections); the below-threshold skills branch; plan mode; build-from-plan;
23
- * and the minimal prompt (no instructions, so the built-in assistant's). Plan
24
- * mode and build-from-plan are mutually exclusive in production, so each has
25
- * its own golden.
18
+ * The shapes: everything at once (the multi-entry workspace, nine skills,
19
+ * channel templates, all five standing sections); plan mode; build-from-plan;
20
+ * the minimal prompt (no instructions, so the built-in assistant's); and the
21
+ * turn's user message for the everything shape (its input files with a
22
+ * rename and a download URL, the vision disclosure, its referenced files and
23
+ * a catchup). Plan mode and build-from-plan are mutually exclusive in
24
+ * production, so each has its own golden.
26
25
  *
27
- * The goldens are taken through `turn-setup.ts` `composeSystemPrompt`, the
28
- * production mapping from the runtime's resolved record to the builder's
29
- * input, so they pin the mapping too, not a copy of it. (The first cut
26
+ * The goldens are taken through `turn-setup.ts` `composeSystemPrompt` and
27
+ * `composeTurnMessage`, the production mappings from the runtime's resolved
28
+ * record to the builder's input, so they pin the mappings too, not copies. (The first cut
30
29
  * photographed the prompt through a field-for-field copy of the mapping as
31
30
  * it then sat inline in `buildEngine`; the next extracted it and re-took the
32
31
  * goldens through the
@@ -43,6 +42,12 @@
43
42
  * a local-path entry names no host path; the multi-entry path sentence
44
43
  * names the virtual root; an entry's tree and the Input Files section end
45
44
  * with one blank line before the next heading.
45
+ * - 2026-09-29 (the native system prompt is the session's, never the turn's):
46
+ * every mounted skill is described whatever the count, so the relevance
47
+ * filter's `### Also Available` note and the below-threshold golden are
48
+ * gone; the Referenced Files and Input Files sections leave the system
49
+ * prompt for the turn's message, which has its own golden; a cloned entry
50
+ * names its branch and no commit.
46
51
  */
47
52
 
48
53
  import { describe, it, expect } from "vitest";
@@ -62,7 +67,7 @@ import type { SkillMetadata } from "../../../shared/skill-resolver.js";
62
67
  import type { RecalledMemoriesContent } from "../../../shared/recalled-memories.js";
63
68
  import type { ProvisionResult } from "../../../shared/workspace/types.js";
64
69
  import { buildEnhancedSystemPrompt } from "../prompt-builder.js";
65
- import { composeSystemPrompt } from "../turn-setup.js";
70
+ import { composeSystemPrompt, composeTurnMessage } from "../turn-setup.js";
66
71
 
67
72
  // ---------------------------------------------------------------------------
68
73
  // Fixtures — every value is a plain fact a golden can name.
@@ -74,7 +79,7 @@ function skill(name: string, description: string): SkillMetadata {
74
79
  return { name, description, path: `.stigmer/skills/${name}/SKILL.md` };
75
80
  }
76
81
 
77
- /** Nine skills: three the message's terms reach, six it does not — the filter has something to exclude. */
82
+ /** Nine skills, past the eight at which the native harness once described only those the message reached: every one is described. */
78
83
  const NINE_SKILLS: readonly SkillMetadata[] = [
79
84
  skill("k8s-deploy", "Deploy services to kubernetes clusters with helm charts"),
80
85
  skill("release-notes", "Draft release notes from the merged pull requests"),
@@ -152,6 +157,7 @@ interface EverythingShape {
152
157
  readonly interactionMode?: InteractionMode;
153
158
  readonly buildFromPlan?: boolean;
154
159
  readonly inputFiles?: readonly ResolvedAttachment[];
160
+ readonly conversationCatchup?: string;
155
161
  }
156
162
 
157
163
  /**
@@ -205,7 +211,7 @@ function everythingInput(shape: EverythingShape): TurnInput {
205
211
  senderIdentity: { value: "15550001111", kind: "whatsapp_phone" },
206
212
  sessionContext: "The user is the on-call engineer this week.",
207
213
  declaredPreferences: { orgContext: "We deploy to eu-west-1.", userContext: "Keep answers terse." },
208
- conversationCatchup: undefined,
214
+ conversationCatchup: shape.conversationCatchup,
209
215
  selectRecalledMemories: async () => RECALLED,
210
216
  },
211
217
  });
@@ -221,16 +227,11 @@ async function systemPromptOf(input: TurnInput): Promise<string> {
221
227
  // ---------------------------------------------------------------------------
222
228
 
223
229
  describe("native system prompt goldens", () => {
224
- it("everything at once, nine skills so the relevance filter fires", async () => {
230
+ it("everything at once, nine skills, every one described", async () => {
225
231
  const prompt = await systemPromptOf(everythingInput({ skills: NINE_SKILLS }));
226
232
  await expect(prompt).toMatchFileSnapshot("./goldens/system-prompt.everything.prompt.md");
227
233
  });
228
234
 
229
- it("three skills, below the relevance threshold: every skill listed, no also-available note", async () => {
230
- const prompt = await systemPromptOf(everythingInput({ skills: THREE_SKILLS }));
231
- await expect(prompt).toMatchFileSnapshot("./goldens/system-prompt.skills-below-threshold.prompt.md");
232
- });
233
-
234
235
  it("plan mode appends the shared directive plus the native read-boundary sentence, last", async () => {
235
236
  const prompt = await systemPromptOf(everythingInput({ skills: THREE_SKILLS, interactionMode: InteractionMode.PLAN }));
236
237
  await expect(prompt).toMatchFileSnapshot("./goldens/system-prompt.plan-mode.prompt.md");
@@ -249,10 +250,14 @@ describe("native system prompt goldens", () => {
249
250
  provisionResults: [],
250
251
  containerRoot: "",
251
252
  skillsPromptSection: "",
252
- workspaceFileRefs: [],
253
- workspaceRoot: "/ws",
254
- inputFiles: [],
255
253
  });
256
254
  await expect(prompt).toMatchFileSnapshot("./goldens/system-prompt.minimal.prompt.md");
257
255
  });
256
+
257
+ it("the turn's user message: the payload first, then the catchup, then what the user typed", async () => {
258
+ const message = composeTurnMessage(
259
+ everythingInput({ skills: NINE_SKILLS, conversationCatchup: "Customer: is the staging deploy done?\nTeammate: Yes, it went out at noon." }),
260
+ );
261
+ await expect(message).toMatchFileSnapshot("./goldens/user-message.everything.md");
262
+ });
258
263
  });