@jr2/machines 0.3.0 → 0.4.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jr2/machines",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "The Machines the jr2 kit ships: packaged xstate Machines that leave their parts Open for a composer to bind.",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -35,20 +35,20 @@
35
35
  "peerDependencies": {
36
36
  "xstate": "^5.18.0",
37
37
  "zod": "^4.4.3",
38
- "@jr2/orchestrator": "0.3.0"
38
+ "@jr2/orchestrator": "0.4.1"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/node": "^26.0.1",
42
42
  "typescript": "^5.6.0",
43
43
  "xstate": "^5.18.0",
44
44
  "zod": "^4.4.3",
45
- "@jr2/orchestrator": "0.3.0"
45
+ "@jr2/orchestrator": "0.4.1"
46
46
  },
47
47
  "files": [
48
48
  "src"
49
49
  ],
50
50
  "scripts": {
51
51
  "typecheck": "tsc --noEmit",
52
- "test": "node --test"
52
+ "test": "node --test --test-global-setup=../../scripts/test-tmpdir.ts"
53
53
  }
54
54
  }
package/src/index.ts CHANGED
@@ -3,8 +3,8 @@
3
3
  // Machines from this index: `import { task } from "@jr2/machines"`.
4
4
  //
5
5
  // It ships MACHINES and never Workflows. A Workflow is the name an Instance registers a Machine
6
- // under, and only the Instance's `workflows/` file can do that (CONTEXT.md) — so what a consumer
7
- // receives is a Machine object, and the line that turns it into a Workflow is theirs:
6
+ // under, and only the Instance's `workflows/` file can do that (GLOSSARY.md, ADR-0009) — so what a
7
+ // consumer receives is a Machine object, and the line that turns it into a Workflow is theirs:
8
8
  //
9
9
  // import { customize } from "@jr2/orchestrator";
10
10
  // import { task } from "@jr2/machines";
@@ -21,7 +21,7 @@
21
21
  // rejected for working by the ACCIDENT of equal versions and installing two copies silently on
22
22
  // skew (ADR-0054).
23
23
  //
24
- // Every Machine exported here leaves its unfillable parts OPEN (CONTEXT.md, ADR-0054): a package
24
+ // Every Machine exported here leaves its unfillable parts OPEN (GLOSSARY.md, ADR-0054): a package
25
25
  // cannot know the repository or pay for the model, so it says so rather than guessing. `jr2 up`
26
26
  // refuses an Open part nobody bound and prints the `customize` line that binds it.
27
27
 
package/src/task.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // done. It is the smallest shape that is still worth packaging — a coder in a Sandbox, a Gate a
3
3
  // human answers, and a loop between them that ends when the human ends it.
4
4
  //
5
- // Both of the parts a package cannot honestly fill are left OPEN (CONTEXT.md, ADR-0051/0054): the
5
+ // Both of the parts a package cannot honestly fill are left OPEN (GLOSSARY.md, ADR-0051/0054): the
6
6
  // Repo Slots are Open as a MAP, because the package cannot know the repository — nor how many
7
7
  // checkouts the task wants beside it — and the Agent `coder` has no model, because the package
8
8
  // cannot pay for one. A consumer binds both on the line that registers this Machine as a
@@ -133,7 +133,9 @@ type BodyContext = BodyInput & {
133
133
  turns: number;
134
134
  /** The coder's own account of the last finished Turn — the Gate's `summary`. */
135
135
  summary?: string;
136
- /** Why the run is parked without a summary: the reason the fault carried — the Gate's `reason`. */
136
+ /** Why the run is parked without a summary: the reason the fault carried — the Gate's `reason`.
137
+ * Kept into the re-briefed Turn that follows, which tells the coder of a memory kill (ADR-0061);
138
+ * the next `finish` clears it. */
137
139
  reason?: string;
138
140
  /** The human's last notes, which are the next Turn's prompt. */
139
141
  notes?: string;
@@ -246,7 +248,7 @@ export const body = jr2Setup({
246
248
  // next one starts, so a count jr2 chose would only ever interrupt them.
247
249
  request_changes: {
248
250
  target: "working",
249
- actions: assign({ notes: ({ event }) => event.notes, summary: undefined, reason: undefined }),
251
+ actions: assign({ notes: ({ event }) => event.notes, summary: undefined }),
250
252
  },
251
253
  },
252
254
  },
@@ -321,6 +323,8 @@ function branchOf(input: TaskInput): string {
321
323
  function coderPrompt(context: BodyContext): string {
322
324
  const notes = context.notes ? `\n\nThe human reviewed your work and asks for changes:\n${context.notes}` : "";
323
325
  if (context.turns > 0) return notes.trimStart() || "Continue.";
326
+ const fault = memoryKill(context.reason);
327
+ const ended = fault ? `\n\nThe last attempt ended: ${fault}` : "";
324
328
  const { branch, repos } = context.workspace;
325
329
  const worktree = worktreeOf(context);
326
330
  const beside = Object.entries(repos)
@@ -328,13 +332,25 @@ function coderPrompt(context: BodyContext): string {
328
332
  .map(([slot, path]) => `\n- ${slot}: ${path}`)
329
333
  .join("");
330
334
  return (
331
- `${context.prompt}${notes}\n\n` +
335
+ `${context.prompt}${ended}${notes}\n\n` +
332
336
  `Work in ${worktree.path}, on branch ${branch} — your tools are rooted there, so a relative ` +
333
337
  `path lands inside it. Commit what you do there, then call finish.` +
334
338
  (beside ? `\n\nAlso checked out beside it, for you to read:${beside}` : "")
335
339
  );
336
340
  }
337
341
 
342
+ /**
343
+ * A memory kill's named line (ADR-0061, layer 5), or undefined for any other fault. The one fault
344
+ * the coder can act on — fewer workers, smaller steps — so the retry Turn carries it: the example
345
+ * for authors. The Orchestrator names it with the fixed prefix `memory limit` on the first line;
346
+ * the lines after it are the transport's words, not the coder's business. Any other fault (a
347
+ * provider down, a runaway rerolled) is the human's, and stays on the Gate alone.
348
+ */
349
+ function memoryKill(reason: string | undefined): string | undefined {
350
+ const line = reason?.split("\n")[0]?.trim();
351
+ return line?.startsWith("memory limit") ? line : undefined;
352
+ }
353
+
338
354
  /**
339
355
  * The checkout the coder edits: the FIRST slot the consumer wrote. This is `task`'s convention,
340
356
  * not the kit's — the handles keep the slots in declaration order and give none a meaning