@tagma/sdk 0.4.15 → 0.4.16

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/dist/dag.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"dag.d.ts","sourceRoot":"","sources":["../src/dag.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,iBAAiB,EACjB,aAAa,EACb,UAAU,EACV,WAAW,EACZ,MAAM,SAAS,CAAC;AAGjB,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC;;;;;;;OAOG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;CACxC;AAED,MAAM,WAAW,GAAG;IAClB,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7C,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,GAAG,CAoHpD;AAID,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC;AAED,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAChD,4EAA4E;IAC5E,QAAQ,CAAC,KAAK,EAAE,SAAS;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC3E;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAiD7D"}
1
+ {"version":3,"file":"dag.d.ts","sourceRoot":"","sources":["../src/dag.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,iBAAiB,EACjB,aAAa,EACb,UAAU,EACV,WAAW,EACZ,MAAM,SAAS,CAAC;AAGjB,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC;;;;;;;OAOG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;CACxC;AAED,MAAM,WAAW,GAAG;IAClB,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7C,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,GAAG,CA4IpD;AAID,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC;AAED,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAChD,4EAA4E;IAC5E,QAAQ,CAAC,KAAK,EAAE,SAAS;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC3E;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAiD7D"}
package/dist/dag.js CHANGED
@@ -42,17 +42,23 @@ export function buildDag(config) {
42
42
  }
43
43
  }
44
44
  if (task.continue_from) {
45
- let resolved;
46
- try {
47
- resolved = resolveRef(task.continue_from, track.id);
45
+ // Preserve the ambiguous-vs-not-found distinction in the user-facing
46
+ // error: rewording "ambiguous" as "no such task found" (the previous
47
+ // behavior) hid the real problem and sent users searching for a
48
+ // missing task that actually existed in two places.
49
+ const result = resolveTaskRef(task.continue_from, track.id, index);
50
+ if (result.kind === 'ambiguous') {
51
+ throw new Error(`Task "${qid}": continue_from "${task.continue_from}" is ambiguous — ` +
52
+ `multiple tracks have a task with this id. Use the fully-qualified ` +
53
+ `form "trackId.${task.continue_from}".`);
48
54
  }
49
- catch {
55
+ if (result.kind === 'not_found') {
50
56
  throw new Error(`Task "${qid}": continue_from "${task.continue_from}" — no such task found. ` +
51
57
  `Use a fully-qualified reference (trackId.taskId) or ensure the target task exists.`);
52
58
  }
53
- resolvedContinueFrom = resolved;
54
- if (!deps.includes(resolved)) {
55
- deps.push(resolved); // continue_from implies dependency
59
+ resolvedContinueFrom = result.qid;
60
+ if (!deps.includes(result.qid)) {
61
+ deps.push(result.qid); // continue_from implies dependency
56
62
  }
57
63
  }
58
64
  // Replace node with resolved deps + qualified continue_from.
@@ -73,23 +79,40 @@ export function buildDag(config) {
73
79
  inDegree.set(id, (inDegree.get(id) ?? 0) + 1);
74
80
  }
75
81
  }
76
- const queue = [];
82
+ // D20: deterministic topo order. Kahn's algorithm dequeues in insertion
83
+ // order by default, which depends on map iteration order — itself a
84
+ // function of the (track, task) order in the YAML. Two pipelines that
85
+ // are DAG-equivalent but written in a different order produced different
86
+ // `sorted` arrays, leading to subtle run-to-run non-determinism for
87
+ // parallel tasks with side effects (writing the same file, touching the
88
+ // same repo). Break the tie by qualified id so identical DAG shapes
89
+ // always yield identical schedules across machines and across YAML
90
+ // round-trips.
91
+ const ready = [];
77
92
  for (const [id, degree] of inDegree) {
78
93
  if (degree === 0)
79
- queue.push(id);
94
+ ready.push(id);
80
95
  }
96
+ ready.sort();
81
97
  const sorted = [];
82
- // Use an index pointer instead of shift() to avoid O(n) per dequeue.
83
98
  let qi = 0;
84
- while (qi < queue.length) {
85
- const current = queue[qi++];
99
+ while (qi < ready.length) {
100
+ const current = ready[qi++];
86
101
  sorted.push(current);
102
+ // Collect children whose in-degree hits zero in this step, then push
103
+ // them into the ready bucket in sorted order — keeps each "wave" of
104
+ // parallel-eligible tasks ordered by qid.
105
+ const newlyReady = [];
87
106
  for (const child of adjacency.get(current)) {
88
107
  const newDegree = inDegree.get(child) - 1;
89
108
  inDegree.set(child, newDegree);
90
109
  if (newDegree === 0)
91
- queue.push(child);
110
+ newlyReady.push(child);
92
111
  }
112
+ if (newlyReady.length > 1)
113
+ newlyReady.sort();
114
+ for (const child of newlyReady)
115
+ ready.push(child);
93
116
  }
94
117
  if (sorted.length !== nodes.size) {
95
118
  // Only report nodes that are actually part of cycles (in-degree > 0
package/dist/dag.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"dag.js","sourceRoot":"","sources":["../src/dag.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dag.js","sourceRoot":"","sources":["../src/dag.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,cAAc,EACd,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACf,MAAM,SAAS,CAAC;AAEjB,KAAK,UAAU,GAAG,YAAY,GAAG,aAAa,GAAG,gBAAgB,GAAG,gBAAgB,CAAC;AAuFrF,MAAM,MAAM,cAAc,GAAG,YAAY,GAAG,UAAU,GAAG,WAAW,CAAC;AAErE;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,UAAU,EACjD,QAAQ,EAAE,cAAc,EACxB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,CAAC,GACT,cAAc,CAchB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAGhF;AAED,wBAAgB,UAAU,CAAC,CAAC,SAAS,UAAU,EAAE,QAAQ,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,CAS1F;AAED,wBAAgB,UAAU,CAAC,QAAQ,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAE1E;AAKD,eAAO,MAAM,cAAc,QAA4D,CAAC;AAExF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,MAAM,CAE/D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,cAAc,GAAG,IAAI,CAmB1E;AAED;;;;;;;;;GASG;AACH,wBAAsB,WAAW,CAC/B,WAAW,EAAE,SAAS,MAAM,EAAE,EAC9B,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC,CAyBf;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,cAAc,GAAG,MAAM,EAAE,CAEjE"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,cAAc,EACd,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACf,MAAM,SAAS,CAAC;AAEjB,KAAK,UAAU,GAAG,YAAY,GAAG,aAAa,GAAG,gBAAgB,GAAG,gBAAgB,CAAC;AAuFrF,MAAM,MAAM,cAAc,GAAG,YAAY,GAAG,UAAU,GAAG,WAAW,CAAC;AAErE;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,UAAU,EACjD,QAAQ,EAAE,cAAc,EACxB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,CAAC,GACT,cAAc,CA2BhB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAGhF;AAED,wBAAgB,UAAU,CAAC,CAAC,SAAS,UAAU,EAAE,QAAQ,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,CAS1F;AAED,wBAAgB,UAAU,CAAC,QAAQ,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAE1E;AAKD,eAAO,MAAM,cAAc,QAA4D,CAAC;AAExF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,MAAM,CAE/D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,cAAc,GAAG,IAAI,CAmB1E;AAED;;;;;;;;;GASG;AACH,wBAAsB,WAAW,CAC/B,WAAW,EAAE,SAAS,MAAM,EAAE,EAC9B,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC,CAyBf;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,cAAc,GAAG,MAAM,EAAE,CAEjE"}
package/dist/registry.js CHANGED
@@ -102,6 +102,17 @@ export function registerPlugin(category, type, handler) {
102
102
  return 'unchanged';
103
103
  const wasReplaced = existing !== undefined;
104
104
  registry.set(type, handler);
105
+ if (wasReplaced) {
106
+ // D18: surface silent shadowing. Hot-reload flows legitimately replace
107
+ // handlers; installing two different plugin packages that both claim
108
+ // the same (category, type) does not — the second wins and breaks the
109
+ // first's consumers with no audit trail. A console.warn is cheap,
110
+ // respects existing callers that rely on 'replaced', and gives ops a
111
+ // grep-able signal when registrations collide unexpectedly.
112
+ // eslint-disable-next-line no-console
113
+ console.warn(`[tagma-sdk] registerPlugin: replaced existing ${category}/${type} — ` +
114
+ `check for duplicate plugin packages claiming the same type.`);
115
+ }
105
116
  return wasReplaced ? 'replaced' : 'registered';
106
117
  }
107
118
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAYzC,MAAM,gBAAgB,GAAgC,IAAI,GAAG,CAAC;IAC5D,SAAS;IACT,UAAU;IACV,aAAa;IACb,aAAa;CACd,CAAC,CAAC;AAEH,MAAM,UAAU,GAAG;IACjB,OAAO,EAAE,IAAI,GAAG,EAAwB;IACxC,QAAQ,EAAE,IAAI,GAAG,EAAyB;IAC1C,WAAW,EAAE,IAAI,GAAG,EAA4B;IAChD,WAAW,EAAE,IAAI,GAAG,EAA4B;CACjD,CAAC;AAEF;;;;;;;;;GASG;AACH,SAAS,gBAAgB,CAAC,QAAwB,EAAE,OAAgB;IAClE,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC5C,MAAM,IAAI,KAAK,CAAC,gCAAgC,QAAQ,qBAAqB,CAAC,CAAC;IACjF,CAAC;IACD,MAAM,CAAC,GAAG,OAAkC,CAAC;IAC7C,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtD,MAAM,IAAI,KAAK,CAAC,gCAAgC,QAAQ,mCAAmC,CAAC,CAAC;IAC/F,CAAC;IACD,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,SAAS,CAAC,CAAC,CAAC;YACf,IAAI,OAAO,CAAC,CAAC,YAAY,KAAK,UAAU,EAAE,CAAC;gBACzC,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC,IAAI,8BAA8B,CAAC,CAAC;YAC3E,CAAC;YACD,mEAAmE;YACnE,+BAA+B;YAC/B,IAAI,IAAa,CAAC;YAClB,IAAI,CAAC;gBACH,IAAI,GAAG,CAAC,CAAC,YAAY,CAAC;YACxB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,IAAI,KAAK,CACb,mBAAmB,CAAC,CAAC,IAAI,iCAAiC;oBACxD,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CACrD,CAAC;YACJ,CAAC;YACD,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACtC,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC,IAAI,oCAAoC,CAAC,CAAC;YACjF,CAAC;YACD,MAAM,CAAC,GAAG,IAA+B,CAAC;YAC1C,KAAK,MAAM,KAAK,IAAI,CAAC,eAAe,EAAE,cAAc,EAAE,cAAc,CAAU,EAAE,CAAC;gBAC/E,IAAI,OAAO,CAAC,CAAC,KAAK,CAAC,KAAK,SAAS,EAAE,CAAC;oBAClC,MAAM,IAAI,KAAK,CACb,mBAAmB,CAAC,CAAC,IAAI,kBAAkB,KAAK,2BAA2B,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,CAC9F,CAAC;gBACJ,CAAC;YACH,CAAC;YACD,sDAAsD;YACtD,KAAK,MAAM,GAAG,IAAI,CAAC,aAAa,EAAE,cAAc,EAAE,cAAc,CAAU,EAAE,CAAC;gBAC3E,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,SAAS,IAAI,OAAO,CAAC,CAAC,GAAG,CAAC,KAAK,UAAU,EAAE,CAAC;oBACzD,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC,IAAI,KAAK,GAAG,kCAAkC,CAAC,CAAC;gBACvF,CAAC;YACH,CAAC;YACD,MAAM;QACR,CAAC;QACD,KAAK,UAAU;YACb,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;gBAClC,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAC,IAAI,uBAAuB,CAAC,CAAC;YACrE,CAAC;YACD,MAAM;QACR,KAAK,aAAa;YAChB,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;gBAClC,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC,IAAI,uBAAuB,CAAC,CAAC;YACxE,CAAC;YACD,MAAM;QACR,KAAK,aAAa;YAChB,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;gBACpC,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC,IAAI,yBAAyB,CAAC,CAAC;YAC1E,CAAC;YACD,MAAM;IACV,CAAC;AACH,CAAC;AAID;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAC5B,QAAwB,EACxB,IAAY,EACZ,OAAU;IAEV,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,4BAA4B,QAAQ,GAAG,CAAC,CAAC;IAC3D,CAAC;IACD,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CAAC,qDAAqD,QAAQ,IAAI,CAAC,CAAC;IACrF,CAAC;IACD,gBAAgB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACpC,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAmB,CAAC;IACxD,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,QAAQ,KAAK,OAAO;QAAE,OAAO,WAAW,CAAC;IAC7C,MAAM,WAAW,GAAG,QAAQ,KAAK,SAAS,CAAC;IAC3C,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC5B,OAAO,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC;AACjD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAwB,EAAE,IAAY;IACrE,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IAClD,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,UAAU,CAAuB,QAAwB,EAAE,IAAY;IACrF,MAAM,OAAO,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC/C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,GAAG,QAAQ,UAAU,IAAI,qBAAqB;YAC5C,sCAAsC,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,EAAE,CAC7E,CAAC;IACJ,CAAC;IACD,OAAO,OAAY,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,QAAwB,EAAE,IAAY;IAC/D,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACxC,CAAC;AAED,wEAAwE;AACxE,oEAAoE;AACpE,qDAAqD;AACrD,MAAM,CAAC,MAAM,cAAc,GAAG,yDAAyD,CAAC;AAExF,MAAM,UAAU,iBAAiB,CAAC,IAAa;IAC7C,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC/D,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAgB;IACjD,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACzD,MAAM,GAAG,GAAI,OAAmC,CAAC,WAAW,CAAC;IAC7D,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC;IACjF,CAAC;IACD,MAAM,CAAC,GAAG,GAA8B,CAAC;IACzC,MAAM,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC;IAC5B,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC;IACpB,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,QAA0B,CAAC,EAAE,CAAC;QACtF,MAAM,IAAI,KAAK,CACb,uCAAuC,CAAC,GAAG,gBAAgB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAC3G,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CAAC,oDAAoD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC9F,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,QAA0B,EAAE,IAAI,EAAE,CAAC;AACxD,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,WAA8B,EAC9B,WAAoB;IAEpB,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAC/B,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CACb,WAAW,IAAI,uDAAuD;gBACpE,wDAAwD;gBACxD,0CAA0C,CAC7C,CAAC;QACJ,CAAC;QACD,IAAI,SAAS,GAAW,IAAI,CAAC;QAC7B,IAAI,WAAW,EAAE,CAAC;YAChB,wEAAwE;YACxE,wEAAwE;YACxE,sEAAsE;YACtE,oBAAoB;YACpB,MAAM,GAAG,GAAG,aAAa,CAAC,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,GAAG,GAAG,CAAC,CAAC;YACvF,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACnC,SAAS,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC;QAC3C,CAAC;QACD,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC;QACpC,IAAI,CAAC,GAAG,CAAC,cAAc,IAAI,CAAC,GAAG,CAAC,UAAU,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;YAC3D,MAAM,IAAI,KAAK,CAAC,WAAW,IAAI,uDAAuD,CAAC,CAAC;QAC1F,CAAC;QACD,cAAc,CAAC,GAAG,CAAC,cAAc,EAAE,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IAClE,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,QAAwB;IACrD,OAAO,CAAC,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;AAC1C,CAAC"}
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAYzC,MAAM,gBAAgB,GAAgC,IAAI,GAAG,CAAC;IAC5D,SAAS;IACT,UAAU;IACV,aAAa;IACb,aAAa;CACd,CAAC,CAAC;AAEH,MAAM,UAAU,GAAG;IACjB,OAAO,EAAE,IAAI,GAAG,EAAwB;IACxC,QAAQ,EAAE,IAAI,GAAG,EAAyB;IAC1C,WAAW,EAAE,IAAI,GAAG,EAA4B;IAChD,WAAW,EAAE,IAAI,GAAG,EAA4B;CACjD,CAAC;AAEF;;;;;;;;;GASG;AACH,SAAS,gBAAgB,CAAC,QAAwB,EAAE,OAAgB;IAClE,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC5C,MAAM,IAAI,KAAK,CAAC,gCAAgC,QAAQ,qBAAqB,CAAC,CAAC;IACjF,CAAC;IACD,MAAM,CAAC,GAAG,OAAkC,CAAC;IAC7C,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtD,MAAM,IAAI,KAAK,CAAC,gCAAgC,QAAQ,mCAAmC,CAAC,CAAC;IAC/F,CAAC;IACD,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,SAAS,CAAC,CAAC,CAAC;YACf,IAAI,OAAO,CAAC,CAAC,YAAY,KAAK,UAAU,EAAE,CAAC;gBACzC,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC,IAAI,8BAA8B,CAAC,CAAC;YAC3E,CAAC;YACD,mEAAmE;YACnE,+BAA+B;YAC/B,IAAI,IAAa,CAAC;YAClB,IAAI,CAAC;gBACH,IAAI,GAAG,CAAC,CAAC,YAAY,CAAC;YACxB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,IAAI,KAAK,CACb,mBAAmB,CAAC,CAAC,IAAI,iCAAiC;oBACxD,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CACrD,CAAC;YACJ,CAAC;YACD,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACtC,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC,IAAI,oCAAoC,CAAC,CAAC;YACjF,CAAC;YACD,MAAM,CAAC,GAAG,IAA+B,CAAC;YAC1C,KAAK,MAAM,KAAK,IAAI,CAAC,eAAe,EAAE,cAAc,EAAE,cAAc,CAAU,EAAE,CAAC;gBAC/E,IAAI,OAAO,CAAC,CAAC,KAAK,CAAC,KAAK,SAAS,EAAE,CAAC;oBAClC,MAAM,IAAI,KAAK,CACb,mBAAmB,CAAC,CAAC,IAAI,kBAAkB,KAAK,2BAA2B,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,CAC9F,CAAC;gBACJ,CAAC;YACH,CAAC;YACD,sDAAsD;YACtD,KAAK,MAAM,GAAG,IAAI,CAAC,aAAa,EAAE,cAAc,EAAE,cAAc,CAAU,EAAE,CAAC;gBAC3E,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,SAAS,IAAI,OAAO,CAAC,CAAC,GAAG,CAAC,KAAK,UAAU,EAAE,CAAC;oBACzD,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC,IAAI,KAAK,GAAG,kCAAkC,CAAC,CAAC;gBACvF,CAAC;YACH,CAAC;YACD,MAAM;QACR,CAAC;QACD,KAAK,UAAU;YACb,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;gBAClC,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAC,IAAI,uBAAuB,CAAC,CAAC;YACrE,CAAC;YACD,MAAM;QACR,KAAK,aAAa;YAChB,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;gBAClC,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC,IAAI,uBAAuB,CAAC,CAAC;YACxE,CAAC;YACD,MAAM;QACR,KAAK,aAAa;YAChB,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;gBACpC,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC,IAAI,yBAAyB,CAAC,CAAC;YAC1E,CAAC;YACD,MAAM;IACV,CAAC;AACH,CAAC;AAID;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAC5B,QAAwB,EACxB,IAAY,EACZ,OAAU;IAEV,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,4BAA4B,QAAQ,GAAG,CAAC,CAAC;IAC3D,CAAC;IACD,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CAAC,qDAAqD,QAAQ,IAAI,CAAC,CAAC;IACrF,CAAC;IACD,gBAAgB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACpC,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAmB,CAAC;IACxD,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,QAAQ,KAAK,OAAO;QAAE,OAAO,WAAW,CAAC;IAC7C,MAAM,WAAW,GAAG,QAAQ,KAAK,SAAS,CAAC;IAC3C,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC5B,IAAI,WAAW,EAAE,CAAC;QAChB,uEAAuE;QACvE,qEAAqE;QACrE,sEAAsE;QACtE,kEAAkE;QAClE,qEAAqE;QACrE,4DAA4D;QAC5D,sCAAsC;QACtC,OAAO,CAAC,IAAI,CACV,iDAAiD,QAAQ,IAAI,IAAI,KAAK;YACpE,6DAA6D,CAChE,CAAC;IACJ,CAAC;IACD,OAAO,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC;AACjD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAwB,EAAE,IAAY;IACrE,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IAClD,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,UAAU,CAAuB,QAAwB,EAAE,IAAY;IACrF,MAAM,OAAO,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC/C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,GAAG,QAAQ,UAAU,IAAI,qBAAqB;YAC5C,sCAAsC,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,EAAE,CAC7E,CAAC;IACJ,CAAC;IACD,OAAO,OAAY,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,QAAwB,EAAE,IAAY;IAC/D,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACxC,CAAC;AAED,wEAAwE;AACxE,oEAAoE;AACpE,qDAAqD;AACrD,MAAM,CAAC,MAAM,cAAc,GAAG,yDAAyD,CAAC;AAExF,MAAM,UAAU,iBAAiB,CAAC,IAAa;IAC7C,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC/D,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAgB;IACjD,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACzD,MAAM,GAAG,GAAI,OAAmC,CAAC,WAAW,CAAC;IAC7D,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC;IACjF,CAAC;IACD,MAAM,CAAC,GAAG,GAA8B,CAAC;IACzC,MAAM,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC;IAC5B,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC;IACpB,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,QAA0B,CAAC,EAAE,CAAC;QACtF,MAAM,IAAI,KAAK,CACb,uCAAuC,CAAC,GAAG,gBAAgB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAC3G,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CAAC,oDAAoD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC9F,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,QAA0B,EAAE,IAAI,EAAE,CAAC;AACxD,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,WAA8B,EAC9B,WAAoB;IAEpB,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAC/B,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CACb,WAAW,IAAI,uDAAuD;gBACpE,wDAAwD;gBACxD,0CAA0C,CAC7C,CAAC;QACJ,CAAC;QACD,IAAI,SAAS,GAAW,IAAI,CAAC;QAC7B,IAAI,WAAW,EAAE,CAAC;YAChB,wEAAwE;YACxE,wEAAwE;YACxE,sEAAsE;YACtE,oBAAoB;YACpB,MAAM,GAAG,GAAG,aAAa,CAAC,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,GAAG,GAAG,CAAC,CAAC;YACvF,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACnC,SAAS,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC;QAC3C,CAAC;QACD,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC;QACpC,IAAI,CAAC,GAAG,CAAC,cAAc,IAAI,CAAC,GAAG,CAAC,UAAU,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;YAC3D,MAAM,IAAI,KAAK,CAAC,WAAW,IAAI,uDAAuD,CAAC,CAAC;QAC1F,CAAC;QACD,cAAc,CAAC,GAAG,CAAC,cAAc,EAAE,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IAClE,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,QAAwB;IACrD,OAAO,CAAC,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;AAC1C,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=task-ref.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-ref.test.d.ts","sourceRoot":"","sources":["../src/task-ref.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,364 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { TASK_ID_RE, isValidTaskId, qualifyTaskId, isQualifiedRef, buildTaskIndex, resolveTaskRef, AMBIGUOUS, } from './task-ref';
3
+ import { buildDag, buildRawDag } from './dag';
4
+ import { validateRaw } from './validate-raw';
5
+ // ═══ Low-level helpers ═══
6
+ describe('isValidTaskId', () => {
7
+ test('accepts letter-led ids with letters, digits, underscores, hyphens', () => {
8
+ for (const id of ['a', 'A', '_', 'task_1', 'Task-2', '_private', 'a_b-c_1']) {
9
+ expect(isValidTaskId(id)).toBe(true);
10
+ expect(TASK_ID_RE.test(id)).toBe(true);
11
+ }
12
+ });
13
+ test('rejects empty, digit-led, dot-bearing, and whitespace forms', () => {
14
+ for (const id of ['', '1task', 'a.b', 'foo bar', '-leading', 'has/slash', 'dot.']) {
15
+ expect(isValidTaskId(id)).toBe(false);
16
+ }
17
+ });
18
+ test('rejects non-string values', () => {
19
+ expect(isValidTaskId(null)).toBe(false);
20
+ expect(isValidTaskId(undefined)).toBe(false);
21
+ expect(isValidTaskId(42)).toBe(false);
22
+ });
23
+ });
24
+ describe('qualifyTaskId + isQualifiedRef', () => {
25
+ test('qualifyTaskId joins with dot; isQualifiedRef detects dotted form', () => {
26
+ expect(qualifyTaskId('alpha', 'review')).toBe('alpha.review');
27
+ expect(isQualifiedRef('alpha.review')).toBe(true);
28
+ expect(isQualifiedRef('review')).toBe(false);
29
+ });
30
+ });
31
+ // ═══ Index build ═══
32
+ describe('buildTaskIndex', () => {
33
+ test('collects all qualified ids and unique bare ids', () => {
34
+ const cfg = {
35
+ name: 'T',
36
+ tracks: [
37
+ { id: 'alpha', name: 'A', tasks: [{ id: 'plan', prompt: 'p' }] },
38
+ { id: 'beta', name: 'B', tasks: [{ id: 'ship', prompt: 'p' }] },
39
+ ],
40
+ };
41
+ const idx = buildTaskIndex(cfg);
42
+ expect(idx.allQualified.has('alpha.plan')).toBe(true);
43
+ expect(idx.allQualified.has('beta.ship')).toBe(true);
44
+ expect(idx.bareToQualified.get('plan')).toBe('alpha.plan');
45
+ expect(idx.bareToQualified.get('ship')).toBe('beta.ship');
46
+ });
47
+ test('marks bare ids shared across tracks as ambiguous', () => {
48
+ const cfg = {
49
+ name: 'T',
50
+ tracks: [
51
+ { id: 'alpha', name: 'A', tasks: [{ id: 'review', prompt: 'p' }] },
52
+ { id: 'beta', name: 'B', tasks: [{ id: 'review', prompt: 'p' }] },
53
+ ],
54
+ };
55
+ const idx = buildTaskIndex(cfg);
56
+ expect(idx.bareToQualified.get('review')).toBe(AMBIGUOUS);
57
+ expect(idx.allQualified.has('alpha.review')).toBe(true);
58
+ expect(idx.allQualified.has('beta.review')).toBe(true);
59
+ });
60
+ test('tolerates tracks/tasks missing ids (editor in-progress state)', () => {
61
+ const cfg = {
62
+ name: 'T',
63
+ tracks: [
64
+ { id: '', name: 'half-typed', tasks: [{ id: 'x', prompt: 'p' }] },
65
+ { id: 'ok', name: 'OK', tasks: [{ id: '', prompt: 'p' }, { id: 'y', prompt: 'p' }] },
66
+ ],
67
+ };
68
+ const idx = buildTaskIndex(cfg);
69
+ expect(idx.allQualified.size).toBe(1);
70
+ expect(idx.allQualified.has('ok.y')).toBe(true);
71
+ });
72
+ });
73
+ // ═══ Ref resolution ═══
74
+ describe('resolveTaskRef', () => {
75
+ const cfg = {
76
+ name: 'T',
77
+ tracks: [
78
+ {
79
+ id: 'alpha',
80
+ name: 'A',
81
+ tasks: [
82
+ { id: 'plan', prompt: 'p' },
83
+ { id: 'review', prompt: 'p' },
84
+ ],
85
+ },
86
+ {
87
+ id: 'beta',
88
+ name: 'B',
89
+ tasks: [
90
+ { id: 'review', prompt: 'p' },
91
+ { id: 'ship', prompt: 'p' },
92
+ ],
93
+ },
94
+ ],
95
+ };
96
+ const idx = buildTaskIndex(cfg);
97
+ test('fully qualified ref resolves when it exists', () => {
98
+ expect(resolveTaskRef('alpha.plan', 'beta', idx)).toEqual({
99
+ kind: 'resolved',
100
+ qid: 'alpha.plan',
101
+ });
102
+ });
103
+ test('fully qualified ref that does not exist is not_found', () => {
104
+ expect(resolveTaskRef('alpha.ghost', 'beta', idx)).toEqual({
105
+ kind: 'not_found',
106
+ ref: 'alpha.ghost',
107
+ });
108
+ });
109
+ test('bare ref prefers same-track shorthand', () => {
110
+ // "review" exists in both tracks — from beta's perspective, the same-
111
+ // track shadow must win over the cross-track ambiguous pool.
112
+ expect(resolveTaskRef('review', 'beta', idx)).toEqual({
113
+ kind: 'resolved',
114
+ qid: 'beta.review',
115
+ });
116
+ expect(resolveTaskRef('review', 'alpha', idx)).toEqual({
117
+ kind: 'resolved',
118
+ qid: 'alpha.review',
119
+ });
120
+ });
121
+ test('bare ref not in current track is ambiguous when multiple tracks have it', () => {
122
+ const twoForeign = {
123
+ name: 'T',
124
+ tracks: [
125
+ { id: 'a', name: 'A', tasks: [{ id: 'review', prompt: 'p' }] },
126
+ { id: 'b', name: 'B', tasks: [{ id: 'review', prompt: 'p' }] },
127
+ { id: 'c', name: 'C', tasks: [{ id: 'other', prompt: 'p' }] },
128
+ ],
129
+ };
130
+ const idx2 = buildTaskIndex(twoForeign);
131
+ expect(resolveTaskRef('review', 'c', idx2)).toEqual({ kind: 'ambiguous', ref: 'review' });
132
+ });
133
+ test('bare ref unique in the pool resolves cross-track', () => {
134
+ expect(resolveTaskRef('ship', 'alpha', idx)).toEqual({
135
+ kind: 'resolved',
136
+ qid: 'beta.ship',
137
+ });
138
+ });
139
+ test('bare ref nobody has is not_found', () => {
140
+ expect(resolveTaskRef('nowhere', 'alpha', idx)).toEqual({
141
+ kind: 'not_found',
142
+ ref: 'nowhere',
143
+ });
144
+ });
145
+ });
146
+ // ═══ Regression: bug #2 — same bare task id in multiple tracks ═══
147
+ describe('regression: continue_from across same-named tasks (bug #2)', () => {
148
+ test('bare continue_from resolves via same-track shadow — qualified id handed downstream', () => {
149
+ // Two tracks, each with a "review" task and a follower that continues
150
+ // from it. The follower MUST bind to its own track's review via the
151
+ // same-track shorthand, not to the other track's review.
152
+ const resolved = {
153
+ name: 'Same-Bare',
154
+ tracks: [
155
+ {
156
+ id: 'alpha',
157
+ name: 'Alpha',
158
+ tasks: [
159
+ { id: 'review', name: 'Review', prompt: 'do A' },
160
+ {
161
+ id: 'ship',
162
+ name: 'Ship',
163
+ prompt: 'ship A',
164
+ depends_on: ['review'],
165
+ continue_from: 'review',
166
+ },
167
+ ],
168
+ },
169
+ {
170
+ id: 'beta',
171
+ name: 'Beta',
172
+ tasks: [
173
+ { id: 'review', name: 'Review', prompt: 'do B' },
174
+ {
175
+ id: 'ship',
176
+ name: 'Ship',
177
+ prompt: 'ship B',
178
+ depends_on: ['review'],
179
+ continue_from: 'review',
180
+ },
181
+ ],
182
+ },
183
+ ],
184
+ };
185
+ const dag = buildDag(resolved);
186
+ const alphaShip = dag.nodes.get('alpha.ship');
187
+ const betaShip = dag.nodes.get('beta.ship');
188
+ expect(alphaShip.resolvedContinueFrom).toBe('alpha.review');
189
+ expect(betaShip.resolvedContinueFrom).toBe('beta.review');
190
+ // And the dep edges get qualified too, preventing engine map-key misses.
191
+ expect(alphaShip.dependsOn).toContain('alpha.review');
192
+ expect(betaShip.dependsOn).toContain('beta.review');
193
+ });
194
+ test('bare continue_from pointing at a foreign ambiguous task throws', () => {
195
+ const resolved = {
196
+ name: 'Ambiguous',
197
+ tracks: [
198
+ {
199
+ id: 'alpha',
200
+ name: 'Alpha',
201
+ tasks: [
202
+ { id: 'review', name: 'Review', prompt: 'p' },
203
+ // ship has no local "review" so bare "review" is ambiguous.
204
+ { id: 'filler', name: 'Filler', prompt: 'p' },
205
+ ],
206
+ },
207
+ {
208
+ id: 'beta',
209
+ name: 'Beta',
210
+ tasks: [{ id: 'review', name: 'Review', prompt: 'p' }],
211
+ },
212
+ {
213
+ id: 'gamma',
214
+ name: 'Gamma',
215
+ tasks: [
216
+ {
217
+ id: 'ship',
218
+ name: 'Ship',
219
+ prompt: 'ship',
220
+ continue_from: 'review',
221
+ },
222
+ ],
223
+ },
224
+ ],
225
+ };
226
+ expect(() => buildDag(resolved)).toThrow(/ambiguous/i);
227
+ });
228
+ test('qualified continue_from always wins — no same-track-shadow risk', () => {
229
+ const resolved = {
230
+ name: 'Qualified',
231
+ tracks: [
232
+ {
233
+ id: 'alpha',
234
+ name: 'Alpha',
235
+ tasks: [
236
+ { id: 'plan', name: 'Plan', prompt: 'p' },
237
+ {
238
+ id: 'plan_v2',
239
+ name: 'Plan v2',
240
+ prompt: 'p2',
241
+ continue_from: 'plan',
242
+ },
243
+ ],
244
+ },
245
+ {
246
+ id: 'beta',
247
+ name: 'Beta',
248
+ tasks: [
249
+ { id: 'plan', name: 'Plan B', prompt: 'p' },
250
+ {
251
+ id: 'cross',
252
+ name: 'Cross',
253
+ prompt: 'x',
254
+ continue_from: 'alpha.plan',
255
+ },
256
+ ],
257
+ },
258
+ ],
259
+ };
260
+ const dag = buildDag(resolved);
261
+ expect(dag.nodes.get('alpha.plan_v2').resolvedContinueFrom).toBe('alpha.plan');
262
+ expect(dag.nodes.get('beta.cross').resolvedContinueFrom).toBe('alpha.plan');
263
+ });
264
+ });
265
+ // ═══ Regression: bug #10 — editor-generated ids always pass validate-raw ═══
266
+ describe('regression: TASK_ID_RE is the single source of truth (bug #10)', () => {
267
+ test('validateRaw rejects exactly what isValidTaskId rejects', () => {
268
+ // Any string the helper says is invalid must be flagged by validateRaw
269
+ // as an "invalid characters" error — proving they read from the same
270
+ // regex rather than two drifted copies.
271
+ const invalids = ['1bad', 'has.dot', 'with space', '-leading', 'q?mark', ''];
272
+ for (const badId of invalids) {
273
+ const cfg = {
274
+ name: 'T',
275
+ tracks: [
276
+ {
277
+ id: 'ok',
278
+ name: 'OK',
279
+ tasks: [{ id: badId, prompt: 'p' }],
280
+ },
281
+ ],
282
+ };
283
+ const errs = validateRaw(cfg);
284
+ expect(errs.length).toBeGreaterThan(0);
285
+ expect(isValidTaskId(badId)).toBe(false);
286
+ }
287
+ });
288
+ });
289
+ // ═══ Regression: buildDag topo sort is deterministic (bug #13) ═══
290
+ describe('regression: buildDag topo sort is deterministic', () => {
291
+ const base = (trackOrder) => ({
292
+ name: 'Determinism',
293
+ tracks: trackOrder.map((id) => ({
294
+ id,
295
+ name: id,
296
+ tasks: [
297
+ { id: 'a', name: 'A', prompt: 'p' },
298
+ { id: 'b', name: 'B', prompt: 'p', depends_on: ['a'] },
299
+ ],
300
+ })),
301
+ });
302
+ test('parallel tasks with equal depth sort by qid, independent of YAML order', () => {
303
+ const forward = buildDag(base(['alpha', 'beta', 'gamma']));
304
+ const reversed = buildDag(base(['gamma', 'beta', 'alpha']));
305
+ expect(forward.sorted).toEqual(reversed.sorted);
306
+ // And the actual order is alphabetical by qid — every "a" before every "b".
307
+ expect(forward.sorted).toEqual([
308
+ 'alpha.a',
309
+ 'beta.a',
310
+ 'gamma.a',
311
+ 'alpha.b',
312
+ 'beta.b',
313
+ 'gamma.b',
314
+ ]);
315
+ });
316
+ test('diamond dependency still produces a unique sorted order', () => {
317
+ // root -> left, right -> join
318
+ const cfg = {
319
+ name: 'Diamond',
320
+ tracks: [
321
+ {
322
+ id: 't',
323
+ name: 't',
324
+ tasks: [
325
+ { id: 'root', name: 'r', prompt: 'p' },
326
+ { id: 'left', name: 'l', prompt: 'p', depends_on: ['root'] },
327
+ { id: 'right', name: 'r', prompt: 'p', depends_on: ['root'] },
328
+ { id: 'join', name: 'j', prompt: 'p', depends_on: ['left', 'right'] },
329
+ ],
330
+ },
331
+ ],
332
+ };
333
+ const first = buildDag(cfg).sorted;
334
+ const second = buildDag(cfg).sorted;
335
+ expect(first).toEqual(second);
336
+ // root first, join last; left/right in alphabetical order between.
337
+ expect(first[0]).toBe('t.root');
338
+ expect(first[first.length - 1]).toBe('t.join');
339
+ expect(first.indexOf('t.left')).toBeLessThan(first.indexOf('t.right'));
340
+ });
341
+ });
342
+ // ═══ Regression: buildRawDag stays lenient (editor real-time view) ═══
343
+ describe('buildRawDag tolerates unresolved refs', () => {
344
+ test('ambiguous bare continue_from is silently skipped (no edge, no throw)', () => {
345
+ const cfg = {
346
+ name: 'T',
347
+ tracks: [
348
+ { id: 'a', name: 'A', tasks: [{ id: 'review', prompt: 'p' }] },
349
+ { id: 'b', name: 'B', tasks: [{ id: 'review', prompt: 'p' }] },
350
+ {
351
+ id: 'c',
352
+ name: 'C',
353
+ tasks: [{ id: 'use', prompt: 'p', continue_from: 'review' }],
354
+ },
355
+ ],
356
+ };
357
+ const raw = buildRawDag(cfg);
358
+ expect(raw.nodes.size).toBe(3);
359
+ // No edge for the ambiguous ref — the editor panel should prompt the
360
+ // user to qualify it instead of silently linking to the wrong track.
361
+ expect(raw.edges.find((e) => e.to === 'c.use')).toBeUndefined();
362
+ });
363
+ });
364
+ //# sourceMappingURL=task-ref.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-ref.test.js","sourceRoot":"","sources":["../src/task-ref.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAElD,OAAO,EACL,UAAU,EACV,aAAa,EACb,aAAa,EACb,cAAc,EACd,cAAc,EACd,cAAc,EACd,SAAS,GACV,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,4BAA4B;AAE5B,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,IAAI,CAAC,mEAAmE,EAAE,GAAG,EAAE;QAC7E,KAAK,MAAM,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,CAAC;YAC5E,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACrC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzC,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACvE,KAAK,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,CAAC,EAAE,CAAC;YAClF,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACxC,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACrC,MAAM,CAAC,aAAa,CAAC,IAAyB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC7D,MAAM,CAAC,aAAa,CAAC,SAA8B,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClE,MAAM,CAAC,aAAa,CAAC,EAAuB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,gCAAgC,EAAE,GAAG,EAAE;IAC9C,IAAI,CAAC,kEAAkE,EAAE,GAAG,EAAE;QAC5E,MAAM,CAAC,aAAa,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC9D,MAAM,CAAC,cAAc,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClD,MAAM,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,sBAAsB;AAEtB,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,IAAI,CAAC,gDAAgD,EAAE,GAAG,EAAE;QAC1D,MAAM,GAAG,GAAsB;YAC7B,IAAI,EAAE,GAAG;YACT,MAAM,EAAE;gBACN,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE;gBAChE,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE;aAChE;SACF,CAAC;QACF,MAAM,GAAG,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;QAChC,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtD,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrD,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC3D,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC5D,MAAM,GAAG,GAAsB;YAC7B,IAAI,EAAE,GAAG;YACT,MAAM,EAAE;gBACN,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE;gBAClE,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE;aAClE;SACF,CAAC;QACF,MAAM,GAAG,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;QAChC,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC1D,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxD,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACzE,MAAM,GAAG,GAAG;YACV,IAAI,EAAE,GAAG;YACT,MAAM,EAAE;gBACN,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE;gBACjE,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE;aACrF;SAC8B,CAAC;QAClC,MAAM,GAAG,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;QAChC,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACtC,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,yBAAyB;AAEzB,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,MAAM,GAAG,GAAsB;QAC7B,IAAI,EAAE,GAAG;QACT,MAAM,EAAE;YACN;gBACE,EAAE,EAAE,OAAO;gBACX,IAAI,EAAE,GAAG;gBACT,KAAK,EAAE;oBACL,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE;oBAC3B,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE;iBAC9B;aACF;YACD;gBACE,EAAE,EAAE,MAAM;gBACV,IAAI,EAAE,GAAG;gBACT,KAAK,EAAE;oBACL,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE;oBAC7B,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE;iBAC5B;aACF;SACF;KACF,CAAC;IACF,MAAM,GAAG,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;IAEhC,IAAI,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACvD,MAAM,CAAC,cAAc,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC;YACxD,IAAI,EAAE,UAAU;YAChB,GAAG,EAAE,YAAY;SAClB,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAChE,MAAM,CAAC,cAAc,CAAC,aAAa,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC;YACzD,IAAI,EAAE,WAAW;YACjB,GAAG,EAAE,aAAa;SACnB,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,uCAAuC,EAAE,GAAG,EAAE;QACjD,sEAAsE;QACtE,6DAA6D;QAC7D,MAAM,CAAC,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC;YACpD,IAAI,EAAE,UAAU;YAChB,GAAG,EAAE,aAAa;SACnB,CAAC,CAAC;QACH,MAAM,CAAC,cAAc,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC;YACrD,IAAI,EAAE,UAAU;YAChB,GAAG,EAAE,cAAc;SACpB,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,yEAAyE,EAAE,GAAG,EAAE;QACnF,MAAM,UAAU,GAAsB;YACpC,IAAI,EAAE,GAAG;YACT,MAAM,EAAE;gBACN,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE;gBAC9D,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE;gBAC9D,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE;aAC9D;SACF,CAAC;QACF,MAAM,IAAI,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC;QACxC,MAAM,CAAC,cAAc,CAAC,QAAQ,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC5F,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC5D,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC;YACnD,IAAI,EAAE,UAAU;YAChB,GAAG,EAAE,WAAW;SACjB,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC5C,MAAM,CAAC,cAAc,CAAC,SAAS,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC;YACtD,IAAI,EAAE,WAAW;YACjB,GAAG,EAAE,SAAS;SACf,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,oEAAoE;AAEpE,QAAQ,CAAC,4DAA4D,EAAE,GAAG,EAAE;IAC1E,IAAI,CAAC,oFAAoF,EAAE,GAAG,EAAE;QAC9F,sEAAsE;QACtE,oEAAoE;QACpE,yDAAyD;QACzD,MAAM,QAAQ,GAAmB;YAC/B,IAAI,EAAE,WAAW;YACjB,MAAM,EAAE;gBACN;oBACE,EAAE,EAAE,OAAO;oBACX,IAAI,EAAE,OAAO;oBACb,KAAK,EAAE;wBACL,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE;wBAChD;4BACE,EAAE,EAAE,MAAM;4BACV,IAAI,EAAE,MAAM;4BACZ,MAAM,EAAE,QAAQ;4BAChB,UAAU,EAAE,CAAC,QAAQ,CAAC;4BACtB,aAAa,EAAE,QAAQ;yBACxB;qBACF;iBACF;gBACD;oBACE,EAAE,EAAE,MAAM;oBACV,IAAI,EAAE,MAAM;oBACZ,KAAK,EAAE;wBACL,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE;wBAChD;4BACE,EAAE,EAAE,MAAM;4BACV,IAAI,EAAE,MAAM;4BACZ,MAAM,EAAE,QAAQ;4BAChB,UAAU,EAAE,CAAC,QAAQ,CAAC;4BACtB,aAAa,EAAE,QAAQ;yBACxB;qBACF;iBACF;aACF;SACF,CAAC;QACF,MAAM,GAAG,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC/B,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,YAAY,CAAE,CAAC;QAC/C,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,CAAE,CAAC;QAC7C,MAAM,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC5D,MAAM,CAAC,QAAQ,CAAC,oBAAoB,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC1D,yEAAyE;QACzE,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;QACtD,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,gEAAgE,EAAE,GAAG,EAAE;QAC1E,MAAM,QAAQ,GAAmB;YAC/B,IAAI,EAAE,WAAW;YACjB,MAAM,EAAE;gBACN;oBACE,EAAE,EAAE,OAAO;oBACX,IAAI,EAAE,OAAO;oBACb,KAAK,EAAE;wBACL,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE;wBAC7C,4DAA4D;wBAC5D,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE;qBAC9C;iBACF;gBACD;oBACE,EAAE,EAAE,MAAM;oBACV,IAAI,EAAE,MAAM;oBACZ,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;iBACvD;gBACD;oBACE,EAAE,EAAE,OAAO;oBACX,IAAI,EAAE,OAAO;oBACb,KAAK,EAAE;wBACL;4BACE,EAAE,EAAE,MAAM;4BACV,IAAI,EAAE,MAAM;4BACZ,MAAM,EAAE,MAAM;4BACd,aAAa,EAAE,QAAQ;yBACxB;qBACF;iBACF;aACF;SACF,CAAC;QACF,MAAM,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,iEAAiE,EAAE,GAAG,EAAE;QAC3E,MAAM,QAAQ,GAAmB;YAC/B,IAAI,EAAE,WAAW;YACjB,MAAM,EAAE;gBACN;oBACE,EAAE,EAAE,OAAO;oBACX,IAAI,EAAE,OAAO;oBACb,KAAK,EAAE;wBACL,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE;wBACzC;4BACE,EAAE,EAAE,SAAS;4BACb,IAAI,EAAE,SAAS;4BACf,MAAM,EAAE,IAAI;4BACZ,aAAa,EAAE,MAAM;yBACtB;qBACF;iBACF;gBACD;oBACE,EAAE,EAAE,MAAM;oBACV,IAAI,EAAE,MAAM;oBACZ,KAAK,EAAE;wBACL,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE;wBAC3C;4BACE,EAAE,EAAE,OAAO;4BACX,IAAI,EAAE,OAAO;4BACb,MAAM,EAAE,GAAG;4BACX,aAAa,EAAE,YAAY;yBAC5B;qBACF;iBACF;aACF;SACF,CAAC;QACF,MAAM,GAAG,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC/B,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,eAAe,CAAE,CAAC,oBAAoB,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAChF,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,YAAY,CAAE,CAAC,oBAAoB,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC/E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,8EAA8E;AAE9E,QAAQ,CAAC,gEAAgE,EAAE,GAAG,EAAE;IAC9E,IAAI,CAAC,wDAAwD,EAAE,GAAG,EAAE;QAClE,uEAAuE;QACvE,qEAAqE;QACrE,wCAAwC;QACxC,MAAM,QAAQ,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;QAC7E,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;YAC7B,MAAM,GAAG,GAAsB;gBAC7B,IAAI,EAAE,GAAG;gBACT,MAAM,EAAE;oBACN;wBACE,EAAE,EAAE,IAAI;wBACR,IAAI,EAAE,IAAI;wBACV,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;qBACpC;iBACF;aACF,CAAC;YACF,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;YAC9B,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;YACvC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,oEAAoE;AAEpE,QAAQ,CAAC,iDAAiD,EAAE,GAAG,EAAE;IAC/D,MAAM,IAAI,GAAG,CAAC,UAA6B,EAAkB,EAAE,CAAC,CAAC;QAC/D,IAAI,EAAE,aAAa;QACnB,MAAM,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YAC9B,EAAE;YACF,IAAI,EAAE,EAAE;YACR,KAAK,EAAE;gBACL,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE;gBACnC,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC,GAAG,CAAC,EAAE;aACvD;SACF,CAAC,CAAC;KACJ,CAAC,CAAC;IAEH,IAAI,CAAC,wEAAwE,EAAE,GAAG,EAAE;QAClF,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;QAC3D,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;QAC5D,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAChD,4EAA4E;QAC5E,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC;YAC7B,SAAS;YACT,QAAQ;YACR,SAAS;YACT,SAAS;YACT,QAAQ;YACR,SAAS;SACV,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,yDAAyD,EAAE,GAAG,EAAE;QACnE,8BAA8B;QAC9B,MAAM,GAAG,GAAmB;YAC1B,IAAI,EAAE,SAAS;YACf,MAAM,EAAE;gBACN;oBACE,EAAE,EAAE,GAAG;oBACP,IAAI,EAAE,GAAG;oBACT,KAAK,EAAE;wBACL,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE;wBACtC,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC,MAAM,CAAC,EAAE;wBAC5D,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC,MAAM,CAAC,EAAE;wBAC7D,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE;qBACtE;iBACF;aACF;SACF,CAAC;QACF,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;QACnC,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;QACpC,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAC9B,mEAAmE;QACnE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAChC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC/C,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,wEAAwE;AAExE,QAAQ,CAAC,uCAAuC,EAAE,GAAG,EAAE;IACrD,IAAI,CAAC,sEAAsE,EAAE,GAAG,EAAE;QAChF,MAAM,GAAG,GAAsB;YAC7B,IAAI,EAAE,GAAG;YACT,MAAM,EAAE;gBACN,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE;gBAC9D,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE;gBAC9D;oBACE,EAAE,EAAE,GAAG;oBACP,IAAI,EAAE,GAAG;oBACT,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC;iBAC7D;aACF;SACF,CAAC;QACF,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;QAC7B,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC/B,qEAAqE;QACrE,qEAAqE;QACrE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IAClE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tagma/sdk",
3
- "version": "0.4.15",
3
+ "version": "0.4.16",
4
4
  "description": "Local AI task orchestration engine — core SDK for tagma pipelines",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/dag.ts CHANGED
@@ -78,18 +78,27 @@ export function buildDag(config: PipelineConfig): Dag {
78
78
  }
79
79
  }
80
80
  if (task.continue_from) {
81
- let resolved: string;
82
- try {
83
- resolved = resolveRef(task.continue_from, track.id);
84
- } catch {
81
+ // Preserve the ambiguous-vs-not-found distinction in the user-facing
82
+ // error: rewording "ambiguous" as "no such task found" (the previous
83
+ // behavior) hid the real problem and sent users searching for a
84
+ // missing task that actually existed in two places.
85
+ const result = resolveTaskRef(task.continue_from, track.id, index);
86
+ if (result.kind === 'ambiguous') {
87
+ throw new Error(
88
+ `Task "${qid}": continue_from "${task.continue_from}" is ambiguous — ` +
89
+ `multiple tracks have a task with this id. Use the fully-qualified ` +
90
+ `form "trackId.${task.continue_from}".`,
91
+ );
92
+ }
93
+ if (result.kind === 'not_found') {
85
94
  throw new Error(
86
95
  `Task "${qid}": continue_from "${task.continue_from}" — no such task found. ` +
87
96
  `Use a fully-qualified reference (trackId.taskId) or ensure the target task exists.`,
88
97
  );
89
98
  }
90
- resolvedContinueFrom = resolved;
91
- if (!deps.includes(resolved)) {
92
- deps.push(resolved); // continue_from implies dependency
99
+ resolvedContinueFrom = result.qid;
100
+ if (!deps.includes(result.qid)) {
101
+ deps.push(result.qid); // continue_from implies dependency
93
102
  }
94
103
  }
95
104
 
@@ -115,22 +124,37 @@ export function buildDag(config: PipelineConfig): Dag {
115
124
  }
116
125
  }
117
126
 
118
- const queue: string[] = [];
127
+ // D20: deterministic topo order. Kahn's algorithm dequeues in insertion
128
+ // order by default, which depends on map iteration order — itself a
129
+ // function of the (track, task) order in the YAML. Two pipelines that
130
+ // are DAG-equivalent but written in a different order produced different
131
+ // `sorted` arrays, leading to subtle run-to-run non-determinism for
132
+ // parallel tasks with side effects (writing the same file, touching the
133
+ // same repo). Break the tie by qualified id so identical DAG shapes
134
+ // always yield identical schedules across machines and across YAML
135
+ // round-trips.
136
+ const ready: string[] = [];
119
137
  for (const [id, degree] of inDegree) {
120
- if (degree === 0) queue.push(id);
138
+ if (degree === 0) ready.push(id);
121
139
  }
140
+ ready.sort();
122
141
 
123
142
  const sorted: string[] = [];
124
- // Use an index pointer instead of shift() to avoid O(n) per dequeue.
125
143
  let qi = 0;
126
- while (qi < queue.length) {
127
- const current = queue[qi++]!;
144
+ while (qi < ready.length) {
145
+ const current = ready[qi++]!;
128
146
  sorted.push(current);
147
+ // Collect children whose in-degree hits zero in this step, then push
148
+ // them into the ready bucket in sorted order — keeps each "wave" of
149
+ // parallel-eligible tasks ordered by qid.
150
+ const newlyReady: string[] = [];
129
151
  for (const child of adjacency.get(current)!) {
130
152
  const newDegree = inDegree.get(child)! - 1;
131
153
  inDegree.set(child, newDegree);
132
- if (newDegree === 0) queue.push(child);
154
+ if (newDegree === 0) newlyReady.push(child);
133
155
  }
156
+ if (newlyReady.length > 1) newlyReady.sort();
157
+ for (const child of newlyReady) ready.push(child);
134
158
  }
135
159
 
136
160
  if (sorted.length !== nodes.size) {
package/src/registry.ts CHANGED
@@ -124,6 +124,19 @@ export function registerPlugin<T extends PluginType>(
124
124
  if (existing === handler) return 'unchanged';
125
125
  const wasReplaced = existing !== undefined;
126
126
  registry.set(type, handler);
127
+ if (wasReplaced) {
128
+ // D18: surface silent shadowing. Hot-reload flows legitimately replace
129
+ // handlers; installing two different plugin packages that both claim
130
+ // the same (category, type) does not — the second wins and breaks the
131
+ // first's consumers with no audit trail. A console.warn is cheap,
132
+ // respects existing callers that rely on 'replaced', and gives ops a
133
+ // grep-able signal when registrations collide unexpectedly.
134
+ // eslint-disable-next-line no-console
135
+ console.warn(
136
+ `[tagma-sdk] registerPlugin: replaced existing ${category}/${type} — ` +
137
+ `check for duplicate plugin packages claiming the same type.`,
138
+ );
139
+ }
127
140
  return wasReplaced ? 'replaced' : 'registered';
128
141
  }
129
142
 
@@ -0,0 +1,401 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import type { PipelineConfig, RawPipelineConfig } from './types';
3
+ import {
4
+ TASK_ID_RE,
5
+ isValidTaskId,
6
+ qualifyTaskId,
7
+ isQualifiedRef,
8
+ buildTaskIndex,
9
+ resolveTaskRef,
10
+ AMBIGUOUS,
11
+ } from './task-ref';
12
+ import { buildDag, buildRawDag } from './dag';
13
+ import { validateRaw } from './validate-raw';
14
+
15
+ // ═══ Low-level helpers ═══
16
+
17
+ describe('isValidTaskId', () => {
18
+ test('accepts letter-led ids with letters, digits, underscores, hyphens', () => {
19
+ for (const id of ['a', 'A', '_', 'task_1', 'Task-2', '_private', 'a_b-c_1']) {
20
+ expect(isValidTaskId(id)).toBe(true);
21
+ expect(TASK_ID_RE.test(id)).toBe(true);
22
+ }
23
+ });
24
+
25
+ test('rejects empty, digit-led, dot-bearing, and whitespace forms', () => {
26
+ for (const id of ['', '1task', 'a.b', 'foo bar', '-leading', 'has/slash', 'dot.']) {
27
+ expect(isValidTaskId(id)).toBe(false);
28
+ }
29
+ });
30
+
31
+ test('rejects non-string values', () => {
32
+ expect(isValidTaskId(null as unknown as string)).toBe(false);
33
+ expect(isValidTaskId(undefined as unknown as string)).toBe(false);
34
+ expect(isValidTaskId(42 as unknown as string)).toBe(false);
35
+ });
36
+ });
37
+
38
+ describe('qualifyTaskId + isQualifiedRef', () => {
39
+ test('qualifyTaskId joins with dot; isQualifiedRef detects dotted form', () => {
40
+ expect(qualifyTaskId('alpha', 'review')).toBe('alpha.review');
41
+ expect(isQualifiedRef('alpha.review')).toBe(true);
42
+ expect(isQualifiedRef('review')).toBe(false);
43
+ });
44
+ });
45
+
46
+ // ═══ Index build ═══
47
+
48
+ describe('buildTaskIndex', () => {
49
+ test('collects all qualified ids and unique bare ids', () => {
50
+ const cfg: RawPipelineConfig = {
51
+ name: 'T',
52
+ tracks: [
53
+ { id: 'alpha', name: 'A', tasks: [{ id: 'plan', prompt: 'p' }] },
54
+ { id: 'beta', name: 'B', tasks: [{ id: 'ship', prompt: 'p' }] },
55
+ ],
56
+ };
57
+ const idx = buildTaskIndex(cfg);
58
+ expect(idx.allQualified.has('alpha.plan')).toBe(true);
59
+ expect(idx.allQualified.has('beta.ship')).toBe(true);
60
+ expect(idx.bareToQualified.get('plan')).toBe('alpha.plan');
61
+ expect(idx.bareToQualified.get('ship')).toBe('beta.ship');
62
+ });
63
+
64
+ test('marks bare ids shared across tracks as ambiguous', () => {
65
+ const cfg: RawPipelineConfig = {
66
+ name: 'T',
67
+ tracks: [
68
+ { id: 'alpha', name: 'A', tasks: [{ id: 'review', prompt: 'p' }] },
69
+ { id: 'beta', name: 'B', tasks: [{ id: 'review', prompt: 'p' }] },
70
+ ],
71
+ };
72
+ const idx = buildTaskIndex(cfg);
73
+ expect(idx.bareToQualified.get('review')).toBe(AMBIGUOUS);
74
+ expect(idx.allQualified.has('alpha.review')).toBe(true);
75
+ expect(idx.allQualified.has('beta.review')).toBe(true);
76
+ });
77
+
78
+ test('tolerates tracks/tasks missing ids (editor in-progress state)', () => {
79
+ const cfg = {
80
+ name: 'T',
81
+ tracks: [
82
+ { id: '', name: 'half-typed', tasks: [{ id: 'x', prompt: 'p' }] },
83
+ { id: 'ok', name: 'OK', tasks: [{ id: '', prompt: 'p' }, { id: 'y', prompt: 'p' }] },
84
+ ],
85
+ } as unknown as RawPipelineConfig;
86
+ const idx = buildTaskIndex(cfg);
87
+ expect(idx.allQualified.size).toBe(1);
88
+ expect(idx.allQualified.has('ok.y')).toBe(true);
89
+ });
90
+ });
91
+
92
+ // ═══ Ref resolution ═══
93
+
94
+ describe('resolveTaskRef', () => {
95
+ const cfg: RawPipelineConfig = {
96
+ name: 'T',
97
+ tracks: [
98
+ {
99
+ id: 'alpha',
100
+ name: 'A',
101
+ tasks: [
102
+ { id: 'plan', prompt: 'p' },
103
+ { id: 'review', prompt: 'p' },
104
+ ],
105
+ },
106
+ {
107
+ id: 'beta',
108
+ name: 'B',
109
+ tasks: [
110
+ { id: 'review', prompt: 'p' },
111
+ { id: 'ship', prompt: 'p' },
112
+ ],
113
+ },
114
+ ],
115
+ };
116
+ const idx = buildTaskIndex(cfg);
117
+
118
+ test('fully qualified ref resolves when it exists', () => {
119
+ expect(resolveTaskRef('alpha.plan', 'beta', idx)).toEqual({
120
+ kind: 'resolved',
121
+ qid: 'alpha.plan',
122
+ });
123
+ });
124
+
125
+ test('fully qualified ref that does not exist is not_found', () => {
126
+ expect(resolveTaskRef('alpha.ghost', 'beta', idx)).toEqual({
127
+ kind: 'not_found',
128
+ ref: 'alpha.ghost',
129
+ });
130
+ });
131
+
132
+ test('bare ref prefers same-track shorthand', () => {
133
+ // "review" exists in both tracks — from beta's perspective, the same-
134
+ // track shadow must win over the cross-track ambiguous pool.
135
+ expect(resolveTaskRef('review', 'beta', idx)).toEqual({
136
+ kind: 'resolved',
137
+ qid: 'beta.review',
138
+ });
139
+ expect(resolveTaskRef('review', 'alpha', idx)).toEqual({
140
+ kind: 'resolved',
141
+ qid: 'alpha.review',
142
+ });
143
+ });
144
+
145
+ test('bare ref not in current track is ambiguous when multiple tracks have it', () => {
146
+ const twoForeign: RawPipelineConfig = {
147
+ name: 'T',
148
+ tracks: [
149
+ { id: 'a', name: 'A', tasks: [{ id: 'review', prompt: 'p' }] },
150
+ { id: 'b', name: 'B', tasks: [{ id: 'review', prompt: 'p' }] },
151
+ { id: 'c', name: 'C', tasks: [{ id: 'other', prompt: 'p' }] },
152
+ ],
153
+ };
154
+ const idx2 = buildTaskIndex(twoForeign);
155
+ expect(resolveTaskRef('review', 'c', idx2)).toEqual({ kind: 'ambiguous', ref: 'review' });
156
+ });
157
+
158
+ test('bare ref unique in the pool resolves cross-track', () => {
159
+ expect(resolveTaskRef('ship', 'alpha', idx)).toEqual({
160
+ kind: 'resolved',
161
+ qid: 'beta.ship',
162
+ });
163
+ });
164
+
165
+ test('bare ref nobody has is not_found', () => {
166
+ expect(resolveTaskRef('nowhere', 'alpha', idx)).toEqual({
167
+ kind: 'not_found',
168
+ ref: 'nowhere',
169
+ });
170
+ });
171
+ });
172
+
173
+ // ═══ Regression: bug #2 — same bare task id in multiple tracks ═══
174
+
175
+ describe('regression: continue_from across same-named tasks (bug #2)', () => {
176
+ test('bare continue_from resolves via same-track shadow — qualified id handed downstream', () => {
177
+ // Two tracks, each with a "review" task and a follower that continues
178
+ // from it. The follower MUST bind to its own track's review via the
179
+ // same-track shorthand, not to the other track's review.
180
+ const resolved: PipelineConfig = {
181
+ name: 'Same-Bare',
182
+ tracks: [
183
+ {
184
+ id: 'alpha',
185
+ name: 'Alpha',
186
+ tasks: [
187
+ { id: 'review', name: 'Review', prompt: 'do A' },
188
+ {
189
+ id: 'ship',
190
+ name: 'Ship',
191
+ prompt: 'ship A',
192
+ depends_on: ['review'],
193
+ continue_from: 'review',
194
+ },
195
+ ],
196
+ },
197
+ {
198
+ id: 'beta',
199
+ name: 'Beta',
200
+ tasks: [
201
+ { id: 'review', name: 'Review', prompt: 'do B' },
202
+ {
203
+ id: 'ship',
204
+ name: 'Ship',
205
+ prompt: 'ship B',
206
+ depends_on: ['review'],
207
+ continue_from: 'review',
208
+ },
209
+ ],
210
+ },
211
+ ],
212
+ };
213
+ const dag = buildDag(resolved);
214
+ const alphaShip = dag.nodes.get('alpha.ship')!;
215
+ const betaShip = dag.nodes.get('beta.ship')!;
216
+ expect(alphaShip.resolvedContinueFrom).toBe('alpha.review');
217
+ expect(betaShip.resolvedContinueFrom).toBe('beta.review');
218
+ // And the dep edges get qualified too, preventing engine map-key misses.
219
+ expect(alphaShip.dependsOn).toContain('alpha.review');
220
+ expect(betaShip.dependsOn).toContain('beta.review');
221
+ });
222
+
223
+ test('bare continue_from pointing at a foreign ambiguous task throws', () => {
224
+ const resolved: PipelineConfig = {
225
+ name: 'Ambiguous',
226
+ tracks: [
227
+ {
228
+ id: 'alpha',
229
+ name: 'Alpha',
230
+ tasks: [
231
+ { id: 'review', name: 'Review', prompt: 'p' },
232
+ // ship has no local "review" so bare "review" is ambiguous.
233
+ { id: 'filler', name: 'Filler', prompt: 'p' },
234
+ ],
235
+ },
236
+ {
237
+ id: 'beta',
238
+ name: 'Beta',
239
+ tasks: [{ id: 'review', name: 'Review', prompt: 'p' }],
240
+ },
241
+ {
242
+ id: 'gamma',
243
+ name: 'Gamma',
244
+ tasks: [
245
+ {
246
+ id: 'ship',
247
+ name: 'Ship',
248
+ prompt: 'ship',
249
+ continue_from: 'review',
250
+ },
251
+ ],
252
+ },
253
+ ],
254
+ };
255
+ expect(() => buildDag(resolved)).toThrow(/ambiguous/i);
256
+ });
257
+
258
+ test('qualified continue_from always wins — no same-track-shadow risk', () => {
259
+ const resolved: PipelineConfig = {
260
+ name: 'Qualified',
261
+ tracks: [
262
+ {
263
+ id: 'alpha',
264
+ name: 'Alpha',
265
+ tasks: [
266
+ { id: 'plan', name: 'Plan', prompt: 'p' },
267
+ {
268
+ id: 'plan_v2',
269
+ name: 'Plan v2',
270
+ prompt: 'p2',
271
+ continue_from: 'plan',
272
+ },
273
+ ],
274
+ },
275
+ {
276
+ id: 'beta',
277
+ name: 'Beta',
278
+ tasks: [
279
+ { id: 'plan', name: 'Plan B', prompt: 'p' },
280
+ {
281
+ id: 'cross',
282
+ name: 'Cross',
283
+ prompt: 'x',
284
+ continue_from: 'alpha.plan',
285
+ },
286
+ ],
287
+ },
288
+ ],
289
+ };
290
+ const dag = buildDag(resolved);
291
+ expect(dag.nodes.get('alpha.plan_v2')!.resolvedContinueFrom).toBe('alpha.plan');
292
+ expect(dag.nodes.get('beta.cross')!.resolvedContinueFrom).toBe('alpha.plan');
293
+ });
294
+ });
295
+
296
+ // ═══ Regression: bug #10 — editor-generated ids always pass validate-raw ═══
297
+
298
+ describe('regression: TASK_ID_RE is the single source of truth (bug #10)', () => {
299
+ test('validateRaw rejects exactly what isValidTaskId rejects', () => {
300
+ // Any string the helper says is invalid must be flagged by validateRaw
301
+ // as an "invalid characters" error — proving they read from the same
302
+ // regex rather than two drifted copies.
303
+ const invalids = ['1bad', 'has.dot', 'with space', '-leading', 'q?mark', ''];
304
+ for (const badId of invalids) {
305
+ const cfg: RawPipelineConfig = {
306
+ name: 'T',
307
+ tracks: [
308
+ {
309
+ id: 'ok',
310
+ name: 'OK',
311
+ tasks: [{ id: badId, prompt: 'p' }],
312
+ },
313
+ ],
314
+ };
315
+ const errs = validateRaw(cfg);
316
+ expect(errs.length).toBeGreaterThan(0);
317
+ expect(isValidTaskId(badId)).toBe(false);
318
+ }
319
+ });
320
+ });
321
+
322
+ // ═══ Regression: buildDag topo sort is deterministic (bug #13) ═══
323
+
324
+ describe('regression: buildDag topo sort is deterministic', () => {
325
+ const base = (trackOrder: readonly string[]): PipelineConfig => ({
326
+ name: 'Determinism',
327
+ tracks: trackOrder.map((id) => ({
328
+ id,
329
+ name: id,
330
+ tasks: [
331
+ { id: 'a', name: 'A', prompt: 'p' },
332
+ { id: 'b', name: 'B', prompt: 'p', depends_on: ['a'] },
333
+ ],
334
+ })),
335
+ });
336
+
337
+ test('parallel tasks with equal depth sort by qid, independent of YAML order', () => {
338
+ const forward = buildDag(base(['alpha', 'beta', 'gamma']));
339
+ const reversed = buildDag(base(['gamma', 'beta', 'alpha']));
340
+ expect(forward.sorted).toEqual(reversed.sorted);
341
+ // And the actual order is alphabetical by qid — every "a" before every "b".
342
+ expect(forward.sorted).toEqual([
343
+ 'alpha.a',
344
+ 'beta.a',
345
+ 'gamma.a',
346
+ 'alpha.b',
347
+ 'beta.b',
348
+ 'gamma.b',
349
+ ]);
350
+ });
351
+
352
+ test('diamond dependency still produces a unique sorted order', () => {
353
+ // root -> left, right -> join
354
+ const cfg: PipelineConfig = {
355
+ name: 'Diamond',
356
+ tracks: [
357
+ {
358
+ id: 't',
359
+ name: 't',
360
+ tasks: [
361
+ { id: 'root', name: 'r', prompt: 'p' },
362
+ { id: 'left', name: 'l', prompt: 'p', depends_on: ['root'] },
363
+ { id: 'right', name: 'r', prompt: 'p', depends_on: ['root'] },
364
+ { id: 'join', name: 'j', prompt: 'p', depends_on: ['left', 'right'] },
365
+ ],
366
+ },
367
+ ],
368
+ };
369
+ const first = buildDag(cfg).sorted;
370
+ const second = buildDag(cfg).sorted;
371
+ expect(first).toEqual(second);
372
+ // root first, join last; left/right in alphabetical order between.
373
+ expect(first[0]).toBe('t.root');
374
+ expect(first[first.length - 1]).toBe('t.join');
375
+ expect(first.indexOf('t.left')).toBeLessThan(first.indexOf('t.right'));
376
+ });
377
+ });
378
+
379
+ // ═══ Regression: buildRawDag stays lenient (editor real-time view) ═══
380
+
381
+ describe('buildRawDag tolerates unresolved refs', () => {
382
+ test('ambiguous bare continue_from is silently skipped (no edge, no throw)', () => {
383
+ const cfg: RawPipelineConfig = {
384
+ name: 'T',
385
+ tracks: [
386
+ { id: 'a', name: 'A', tasks: [{ id: 'review', prompt: 'p' }] },
387
+ { id: 'b', name: 'B', tasks: [{ id: 'review', prompt: 'p' }] },
388
+ {
389
+ id: 'c',
390
+ name: 'C',
391
+ tasks: [{ id: 'use', prompt: 'p', continue_from: 'review' }],
392
+ },
393
+ ],
394
+ };
395
+ const raw = buildRawDag(cfg);
396
+ expect(raw.nodes.size).toBe(3);
397
+ // No edge for the ambiguous ref — the editor panel should prompt the
398
+ // user to qualify it instead of silently linking to the wrong track.
399
+ expect(raw.edges.find((e) => e.to === 'c.use')).toBeUndefined();
400
+ });
401
+ });