@pmelab/gtd 51.0.0 → 51.0.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gtd",
3
- "version": "51.0.0",
3
+ "version": "51.0.1",
4
4
  "description": "Drive the gtd loop inside Claude Code: agent turns run as subagents, gtd decides every beat",
5
5
  "author": {
6
6
  "name": "Philipp Melab"
@@ -38078,6 +38078,28 @@ const memoryScopeOf = (name) => {
38078
38078
  const dot = name.lastIndexOf(".");
38079
38079
  return dot === -1 ? "" : name.slice(0, dot);
38080
38080
  };
38081
+ /** Is `name` inside `scope`'s subtree? The root scope `""` holds everything. */
38082
+ const inScope = (name, scope) => scope === "" || name === scope || name.startsWith(`${scope}.`);
38083
+ /**
38084
+ * Where the current unbroken run of rests inside `scope`'s subtree began —
38085
+ * a dip into a descendant scope does not break it, a sibling or ancestor
38086
+ * does, and so does a fresh `scope()` call of the same scope.
38087
+ */
38088
+ const scopeRunStart = (trace, scope) => {
38089
+ let start = -1;
38090
+ for (let k = 0; k < trace.length; k++) {
38091
+ if (!inScope(trace[k].memoryScope, scope)) continue;
38092
+ const previous = trace[k - 1];
38093
+ if (!(previous !== void 0 && inScope(previous.memoryScope, scope) && scopeCallOf(previous, scope) === scopeCallOf(trace[k], scope))) start = k;
38094
+ }
38095
+ return start;
38096
+ };
38097
+ /** The innermost `scope()` call enclosing `scope` at `step` — a new call is a new conversation. */
38098
+ const scopeCallOf = (step, scope) => {
38099
+ let call;
38100
+ for (const entry of step.scopeCalls) if (inScope(scope, entry.prefix)) call = entry.call;
38101
+ return call;
38102
+ };
38081
38103
  var Stop = class extends Error {};
38082
38104
  const short = (hash) => hash.slice(0, 7);
38083
38105
  /** One step's changes; content is read only when a flow asks for it. */
@@ -38337,12 +38359,13 @@ const replay = async (input) => {
38337
38359
  skills: step.skills,
38338
38360
  access: unfoldedAccess.get(step)
38339
38361
  };
38340
- const seen = personas.get(step.memoryScope);
38362
+ const conversation = `${step.memoryScope}#${scopeRunStart(trace, step.memoryScope)}`;
38363
+ const seen = personas.get(conversation);
38341
38364
  if (seen === void 0) {
38342
- personas.set(step.memoryScope, identity);
38365
+ personas.set(conversation, identity);
38343
38366
  return;
38344
38367
  }
38345
- return samePersona(seen, identity) ? void 0 : `gtd: "${step.name}" runs with a different model, system prompt, skills or file access than an earlier agent step in memory scope "${step.memoryScope || "root"}" — one scope is one conversation, and a conversation has one identity`;
38368
+ return samePersona(seen, identity) ? void 0 : `gtd: "${step.name}" runs with a different model, system prompt, skills or file access than an earlier agent step in its conversation in memory scope "${step.memoryScope || "root"}" — a conversation has one identity`;
38346
38369
  };
38347
38370
  const DIVERGED = "the workflow changed under this process; run `gtd abandon` to start over";
38348
38371
  const blockerAt = (step) => {
@@ -40110,28 +40133,6 @@ const stepDefOf = (step) => {
40110
40133
  case "human": return messageDef(common, request, step);
40111
40134
  }
40112
40135
  };
40113
- /** Is `name` inside `scope`'s subtree? The root scope `""` holds everything. */
40114
- const inScope = (name, scope) => scope === "" || name === scope || name.startsWith(`${scope}.`);
40115
- /**
40116
- * Where the current unbroken run of rests inside `scope`'s subtree began —
40117
- * a dip into a descendant scope does not break it, a sibling or ancestor
40118
- * does, and so does a fresh `scope()` call of the same scope.
40119
- */
40120
- const scopeRunStart = (trace, scope) => {
40121
- let start = -1;
40122
- for (let k = 0; k < trace.length; k++) {
40123
- if (!inScope(trace[k].memoryScope, scope)) continue;
40124
- const previous = trace[k - 1];
40125
- if (!(previous !== void 0 && inScope(previous.memoryScope, scope) && scopeCallOf(previous, scope) === scopeCallOf(trace[k], scope))) start = k;
40126
- }
40127
- return start;
40128
- };
40129
- /** The innermost `scope()` call enclosing `scope` at `step` — a new call is a new conversation. */
40130
- const scopeCallOf = (step, scope) => {
40131
- let call;
40132
- for (const entry of step.scopeCalls) if (inScope(scope, entry.prefix)) call = entry.call;
40133
- return call;
40134
- };
40135
40136
  const ROOT_MEMORY_SCOPE_NAME = "root";
40136
40137
  /**
40137
40138
  * An agent rest's memory key, `<scope>#<hash7>`: the hash is the commit the
package/dist/testkit.mjs CHANGED
@@ -7124,6 +7124,28 @@ const memoryScopeOf = (name) => {
7124
7124
  const dot = name.lastIndexOf(".");
7125
7125
  return dot === -1 ? "" : name.slice(0, dot);
7126
7126
  };
7127
+ /** Is `name` inside `scope`'s subtree? The root scope `""` holds everything. */
7128
+ const inScope = (name, scope) => scope === "" || name === scope || name.startsWith(`${scope}.`);
7129
+ /**
7130
+ * Where the current unbroken run of rests inside `scope`'s subtree began —
7131
+ * a dip into a descendant scope does not break it, a sibling or ancestor
7132
+ * does, and so does a fresh `scope()` call of the same scope.
7133
+ */
7134
+ const scopeRunStart = (trace, scope) => {
7135
+ let start = -1;
7136
+ for (let k = 0; k < trace.length; k++) {
7137
+ if (!inScope(trace[k].memoryScope, scope)) continue;
7138
+ const previous = trace[k - 1];
7139
+ if (!(previous !== void 0 && inScope(previous.memoryScope, scope) && scopeCallOf(previous, scope) === scopeCallOf(trace[k], scope))) start = k;
7140
+ }
7141
+ return start;
7142
+ };
7143
+ /** The innermost `scope()` call enclosing `scope` at `step` — a new call is a new conversation. */
7144
+ const scopeCallOf = (step, scope) => {
7145
+ let call;
7146
+ for (const entry of step.scopeCalls) if (inScope(scope, entry.prefix)) call = entry.call;
7147
+ return call;
7148
+ };
7127
7149
  var Stop = class extends Error {};
7128
7150
  const short = (hash) => hash.slice(0, 7);
7129
7151
  /** One step's changes; content is read only when a flow asks for it. */
@@ -7383,12 +7405,13 @@ const replay = async (input) => {
7383
7405
  skills: step.skills,
7384
7406
  access: unfoldedAccess.get(step)
7385
7407
  };
7386
- const seen = personas.get(step.memoryScope);
7408
+ const conversation = `${step.memoryScope}#${scopeRunStart(trace, step.memoryScope)}`;
7409
+ const seen = personas.get(conversation);
7387
7410
  if (seen === void 0) {
7388
- personas.set(step.memoryScope, identity);
7411
+ personas.set(conversation, identity);
7389
7412
  return;
7390
7413
  }
7391
- return samePersona(seen, identity) ? void 0 : `gtd: "${step.name}" runs with a different model, system prompt, skills or file access than an earlier agent step in memory scope "${step.memoryScope || "root"}" — one scope is one conversation, and a conversation has one identity`;
7414
+ return samePersona(seen, identity) ? void 0 : `gtd: "${step.name}" runs with a different model, system prompt, skills or file access than an earlier agent step in its conversation in memory scope "${step.memoryScope || "root"}" — a conversation has one identity`;
7392
7415
  };
7393
7416
  const DIVERGED = "the workflow changed under this process; run `gtd abandon` to start over";
7394
7417
  const blockerAt = (step) => {
@@ -9145,28 +9168,6 @@ const stepDefOf = (step) => {
9145
9168
  case "human": return messageDef(common, request, step);
9146
9169
  }
9147
9170
  };
9148
- /** Is `name` inside `scope`'s subtree? The root scope `""` holds everything. */
9149
- const inScope = (name, scope) => scope === "" || name === scope || name.startsWith(`${scope}.`);
9150
- /**
9151
- * Where the current unbroken run of rests inside `scope`'s subtree began —
9152
- * a dip into a descendant scope does not break it, a sibling or ancestor
9153
- * does, and so does a fresh `scope()` call of the same scope.
9154
- */
9155
- const scopeRunStart = (trace, scope) => {
9156
- let start = -1;
9157
- for (let k = 0; k < trace.length; k++) {
9158
- if (!inScope(trace[k].memoryScope, scope)) continue;
9159
- const previous = trace[k - 1];
9160
- if (!(previous !== void 0 && inScope(previous.memoryScope, scope) && scopeCallOf(previous, scope) === scopeCallOf(trace[k], scope))) start = k;
9161
- }
9162
- return start;
9163
- };
9164
- /** The innermost `scope()` call enclosing `scope` at `step` — a new call is a new conversation. */
9165
- const scopeCallOf = (step, scope) => {
9166
- let call;
9167
- for (const entry of step.scopeCalls) if (inScope(scope, entry.prefix)) call = entry.call;
9168
- return call;
9169
- };
9170
9171
  const ROOT_MEMORY_SCOPE_NAME = "root";
9171
9172
  /**
9172
9173
  * An agent rest's memory key, `<scope>#<hash7>`: the hash is the commit the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pmelab/gtd",
3
- "version": "51.0.0",
3
+ "version": "51.0.1",
4
4
  "private": false,
5
5
  "description": "Git-aware CLI that emits the next prompt for an autonomous coding agent based on the current repository state",
6
6
  "bin": {
package/src/edge/Edge.ts CHANGED
@@ -37,6 +37,7 @@ import {
37
37
  isRecordableApproval,
38
38
  parseCommitMessage,
39
39
  replay,
40
+ scopeRunStart,
40
41
  sliceRanges,
41
42
  treeFromRecord,
42
43
  type EpisodeCommit,
@@ -791,36 +792,6 @@ const stepDefOf = (step: ReachedStep): StepDef => {
791
792
  }
792
793
  }
793
794
 
794
- /** Is `name` inside `scope`'s subtree? The root scope `""` holds everything. */
795
- const inScope = (name: string, scope: string): boolean =>
796
- scope === "" || name === scope || name.startsWith(`${scope}.`)
797
-
798
- /**
799
- * Where the current unbroken run of rests inside `scope`'s subtree began —
800
- * a dip into a descendant scope does not break it, a sibling or ancestor
801
- * does, and so does a fresh `scope()` call of the same scope.
802
- */
803
- const scopeRunStart = (trace: readonly ReachedStep[], scope: string): number => {
804
- let start = -1
805
- for (let k = 0; k < trace.length; k++) {
806
- if (!inScope(trace[k]!.memoryScope, scope)) continue
807
- const previous = trace[k - 1]
808
- const unbroken =
809
- previous !== undefined &&
810
- inScope(previous.memoryScope, scope) &&
811
- scopeCallOf(previous, scope) === scopeCallOf(trace[k]!, scope)
812
- if (!unbroken) start = k
813
- }
814
- return start
815
- }
816
-
817
- /** The innermost `scope()` call enclosing `scope` at `step` — a new call is a new conversation. */
818
- const scopeCallOf = (step: ReachedStep, scope: string): number | undefined => {
819
- let call: number | undefined
820
- for (const entry of step.scopeCalls) if (inScope(scope, entry.prefix)) call = entry.call
821
- return call
822
- }
823
-
824
795
  // Shown for the root scope, which has no name of its own.
825
796
  const ROOT_MEMORY_SCOPE_NAME = "root"
826
797
 
@@ -940,7 +940,7 @@ describe("replay: skills resolution", () => {
940
940
  expect(await wireSkills(inScope({ name: "a" }, step), { skills: { a: [] } })).toBeUndefined()
941
941
  })
942
942
 
943
- it("fails two agent steps in one memory scope with different lists", async () => {
943
+ it("runs two scope() calls of one memory scope with different lists as two conversations", async () => {
944
944
  const flow: Flow = async () => {
945
945
  await scope({ name: "a", skills: ["one"] }, async () => {
946
946
  await agent("first", "prompt")
@@ -959,9 +959,7 @@ describe("replay: skills resolution", () => {
959
959
  start: hashOf(0),
960
960
  budgetBytes: 1024,
961
961
  })
962
- expect(outcome.kind === "failed" && outcome.message).toContain(
963
- "different model, system prompt, skills or file access",
964
- )
962
+ expect(outcome.kind).toBe("rest")
965
963
  })
966
964
  })
967
965
 
@@ -1084,7 +1082,7 @@ describe("replay: access resolution", () => {
1084
1082
  expect(outcome.rest.access).toEqual({ read: null, write: ["src/a.ts"] })
1085
1083
  })
1086
1084
 
1087
- it("fails two agent steps in one memory scope with different scope access", async () => {
1085
+ it("runs two scope() calls of one memory scope with different access as two conversations", async () => {
1088
1086
  const flow: Flow = async () => {
1089
1087
  await scope({ name: "a", access: { write: ["one"] } }, () => agent("first", "prompt"))
1090
1088
  await scope({ name: "a", access: { write: ["two"] } }, () => agent("second", "prompt"))
@@ -1092,9 +1090,7 @@ describe("replay: access resolution", () => {
1092
1090
  const history = new History()
1093
1091
  history.land("agent", "a.first", 1, "a.second", { "x.txt": "x" })
1094
1092
  const outcome = await restOf(flow, {}, history)
1095
- expect(outcome.kind === "failed" && outcome.message).toContain(
1096
- "different model, system prompt, skills or file access",
1097
- )
1093
+ expect(outcome.kind).toBe("rest")
1098
1094
  })
1099
1095
 
1100
1096
  it("does not conflict two steps that differ only in steering file", async () => {
@@ -139,6 +139,36 @@ const memoryScopeOf = (name: string): string => {
139
139
  return dot === -1 ? "" : name.slice(0, dot)
140
140
  }
141
141
 
142
+ /** Is `name` inside `scope`'s subtree? The root scope `""` holds everything. */
143
+ const inScope = (name: string, scope: string): boolean =>
144
+ scope === "" || name === scope || name.startsWith(`${scope}.`)
145
+
146
+ /**
147
+ * Where the current unbroken run of rests inside `scope`'s subtree began —
148
+ * a dip into a descendant scope does not break it, a sibling or ancestor
149
+ * does, and so does a fresh `scope()` call of the same scope.
150
+ */
151
+ export const scopeRunStart = (trace: readonly ReachedStep[], scope: string): number => {
152
+ let start = -1
153
+ for (let k = 0; k < trace.length; k++) {
154
+ if (!inScope(trace[k]!.memoryScope, scope)) continue
155
+ const previous = trace[k - 1]
156
+ const unbroken =
157
+ previous !== undefined &&
158
+ inScope(previous.memoryScope, scope) &&
159
+ scopeCallOf(previous, scope) === scopeCallOf(trace[k]!, scope)
160
+ if (!unbroken) start = k
161
+ }
162
+ return start
163
+ }
164
+
165
+ /** The innermost `scope()` call enclosing `scope` at `step` — a new call is a new conversation. */
166
+ const scopeCallOf = (step: ReachedStep, scope: string): number | undefined => {
167
+ let call: number | undefined
168
+ for (const entry of step.scopeCalls) if (inScope(scope, entry.prefix)) call = entry.call
169
+ return call
170
+ }
171
+
142
172
  class Stop extends Error {}
143
173
 
144
174
  const short = (hash: string): string => hash.slice(0, 7)
@@ -505,14 +535,16 @@ export const replay = async (input: ReplayInput): Promise<ReplayOutcome> => {
505
535
  skills: step.skills,
506
536
  access: unfoldedAccess.get(step),
507
537
  }
508
- const seen = personas.get(step.memoryScope)
538
+ // Keyed like the memory key: a fresh scope() call is a fresh conversation, free to differ.
539
+ const conversation = `${step.memoryScope}#${scopeRunStart(trace, step.memoryScope)}`
540
+ const seen = personas.get(conversation)
509
541
  if (seen === undefined) {
510
- personas.set(step.memoryScope, identity)
542
+ personas.set(conversation, identity)
511
543
  return undefined
512
544
  }
513
545
  return samePersona(seen, identity)
514
546
  ? undefined
515
- : `gtd: "${step.name}" runs with a different model, system prompt, skills or file access than an earlier agent step in memory scope "${step.memoryScope || "root"}" — one scope is one conversation, and a conversation has one identity`
547
+ : `gtd: "${step.name}" runs with a different model, system prompt, skills or file access than an earlier agent step in its conversation in memory scope "${step.memoryScope || "root"}" — a conversation has one identity`
516
548
  }
517
549
 
518
550
  const DIVERGED = "the workflow changed under this process; run `gtd abandon` to start over"
@@ -1,4 +1,10 @@
1
- export { replay, type EpisodeCommit, type ReachedStep, type ReplayOutcome } from "./Replay.js"
1
+ export {
2
+ replay,
3
+ scopeRunStart,
4
+ type EpisodeCommit,
5
+ type ReachedStep,
6
+ type ReplayOutcome,
7
+ } from "./Replay.js"
2
8
  export { treeFromRecord, type TreeView } from "./Tree.js"
3
9
  export {
4
10
  formatCommitMessage,