agentopology 0.2.5 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/README.md +121 -7
  2. package/dist/analyzer/__tests__/analyzer.test.js +36 -9
  3. package/dist/analyzer/__tests__/analyzer.test.js.map +1 -1
  4. package/dist/analyzer/__tests__/observability-gap.test.js +40 -10
  5. package/dist/analyzer/__tests__/observability-gap.test.js.map +1 -1
  6. package/dist/analyzer/index.d.ts +2 -1
  7. package/dist/analyzer/index.d.ts.map +1 -1
  8. package/dist/analyzer/index.js +1 -1
  9. package/dist/analyzer/index.js.map +1 -1
  10. package/dist/bindings/__tests__/bindings.test.js +20 -5
  11. package/dist/bindings/__tests__/bindings.test.js.map +1 -1
  12. package/dist/bindings/__tests__/claude-workflow.test.js +12 -3
  13. package/dist/bindings/__tests__/claude-workflow.test.js.map +1 -1
  14. package/dist/bindings/__tests__/defaults-agreement.test.d.ts +16 -0
  15. package/dist/bindings/__tests__/defaults-agreement.test.d.ts.map +1 -0
  16. package/dist/bindings/__tests__/defaults-agreement.test.js +80 -0
  17. package/dist/bindings/__tests__/defaults-agreement.test.js.map +1 -0
  18. package/dist/bindings/__tests__/observability.test.js +8 -2
  19. package/dist/bindings/__tests__/observability.test.js.map +1 -1
  20. package/dist/bindings/__tests__/stub-generation.test.js +8 -2
  21. package/dist/bindings/__tests__/stub-generation.test.js.map +1 -1
  22. package/dist/bindings/claude-code.d.ts.map +1 -1
  23. package/dist/bindings/claude-code.js +102 -3
  24. package/dist/bindings/claude-code.js.map +1 -1
  25. package/dist/bindings/claude-workflow.d.ts.map +1 -1
  26. package/dist/bindings/claude-workflow.js +2 -1
  27. package/dist/bindings/claude-workflow.js.map +1 -1
  28. package/dist/bindings/codex.d.ts.map +1 -1
  29. package/dist/bindings/codex.js +6 -2
  30. package/dist/bindings/codex.js.map +1 -1
  31. package/dist/bindings/gemini-cli.d.ts.map +1 -1
  32. package/dist/bindings/gemini-cli.js +2 -1
  33. package/dist/bindings/gemini-cli.js.map +1 -1
  34. package/dist/bindings/kiro.d.ts.map +1 -1
  35. package/dist/bindings/kiro.js +2 -1
  36. package/dist/bindings/kiro.js.map +1 -1
  37. package/dist/bindings/openclaw.d.ts.map +1 -1
  38. package/dist/bindings/openclaw.js +2 -1
  39. package/dist/bindings/openclaw.js.map +1 -1
  40. package/dist/cli/__tests__/cli.test.d.ts +12 -0
  41. package/dist/cli/__tests__/cli.test.d.ts.map +1 -0
  42. package/dist/cli/__tests__/cli.test.js +171 -0
  43. package/dist/cli/__tests__/cli.test.js.map +1 -0
  44. package/dist/cli/index.d.ts +1 -0
  45. package/dist/cli/index.d.ts.map +1 -1
  46. package/dist/cli/index.js +344 -5
  47. package/dist/cli/index.js.map +1 -1
  48. package/dist/docs/content.d.ts.map +1 -1
  49. package/dist/docs/content.js +112 -0
  50. package/dist/docs/content.js.map +1 -1
  51. package/dist/exporters/ascii.d.ts +13 -0
  52. package/dist/exporters/ascii.d.ts.map +1 -0
  53. package/dist/exporters/ascii.js +26 -0
  54. package/dist/exporters/ascii.js.map +1 -0
  55. package/dist/exporters/brief.d.ts +14 -0
  56. package/dist/exporters/brief.d.ts.map +1 -0
  57. package/dist/exporters/brief.js +37 -0
  58. package/dist/exporters/brief.js.map +1 -0
  59. package/dist/exporters/index.d.ts +2 -0
  60. package/dist/exporters/index.d.ts.map +1 -1
  61. package/dist/exporters/index.js +6 -0
  62. package/dist/exporters/index.js.map +1 -1
  63. package/dist/exporters/markdown.d.ts.map +1 -1
  64. package/dist/exporters/markdown.js +2 -1
  65. package/dist/exporters/markdown.js.map +1 -1
  66. package/dist/exporters/mermaid.d.ts.map +1 -1
  67. package/dist/exporters/mermaid.js +2 -1
  68. package/dist/exporters/mermaid.js.map +1 -1
  69. package/dist/import/__tests__/import.test.js +8 -2
  70. package/dist/import/__tests__/import.test.js.map +1 -1
  71. package/dist/parser/__tests__/parser.test.js +1553 -207
  72. package/dist/parser/__tests__/parser.test.js.map +1 -1
  73. package/dist/parser/__tests__/spec-sync.test.d.ts +10 -0
  74. package/dist/parser/__tests__/spec-sync.test.d.ts.map +1 -0
  75. package/dist/parser/__tests__/spec-sync.test.js +60 -0
  76. package/dist/parser/__tests__/spec-sync.test.js.map +1 -0
  77. package/dist/parser/ast.d.ts +39 -0
  78. package/dist/parser/ast.d.ts.map +1 -1
  79. package/dist/parser/index.d.ts +50 -6
  80. package/dist/parser/index.d.ts.map +1 -1
  81. package/dist/parser/index.js +241 -30
  82. package/dist/parser/index.js.map +1 -1
  83. package/dist/parser/validator.d.ts +7 -1
  84. package/dist/parser/validator.d.ts.map +1 -1
  85. package/dist/parser/validator.js +143 -5
  86. package/dist/parser/validator.js.map +1 -1
  87. package/dist/plan/__tests__/brief.test.d.ts +2 -0
  88. package/dist/plan/__tests__/brief.test.d.ts.map +1 -0
  89. package/dist/plan/__tests__/brief.test.js +442 -0
  90. package/dist/plan/__tests__/brief.test.js.map +1 -0
  91. package/dist/plan/brief.d.ts +207 -0
  92. package/dist/plan/brief.d.ts.map +1 -0
  93. package/dist/plan/brief.js +342 -0
  94. package/dist/plan/brief.js.map +1 -0
  95. package/dist/plan/render.d.ts +20 -0
  96. package/dist/plan/render.d.ts.map +1 -0
  97. package/dist/plan/render.js +425 -0
  98. package/dist/plan/render.js.map +1 -0
  99. package/dist/render/__tests__/ascii.test.d.ts +2 -0
  100. package/dist/render/__tests__/ascii.test.d.ts.map +1 -0
  101. package/dist/render/__tests__/ascii.test.js +171 -0
  102. package/dist/render/__tests__/ascii.test.js.map +1 -0
  103. package/dist/render/ascii.d.ts +23 -0
  104. package/dist/render/ascii.d.ts.map +1 -0
  105. package/dist/render/ascii.js +178 -0
  106. package/dist/render/ascii.js.map +1 -0
  107. package/dist/resolve/__tests__/defaults.test.d.ts +2 -0
  108. package/dist/resolve/__tests__/defaults.test.d.ts.map +1 -0
  109. package/dist/resolve/__tests__/defaults.test.js +109 -0
  110. package/dist/resolve/__tests__/defaults.test.js.map +1 -0
  111. package/dist/resolve/__tests__/order.test.d.ts +2 -0
  112. package/dist/resolve/__tests__/order.test.d.ts.map +1 -0
  113. package/dist/resolve/__tests__/order.test.js +238 -0
  114. package/dist/resolve/__tests__/order.test.js.map +1 -0
  115. package/dist/resolve/__tests__/phase.test.d.ts +12 -0
  116. package/dist/resolve/__tests__/phase.test.d.ts.map +1 -0
  117. package/dist/resolve/__tests__/phase.test.js +81 -0
  118. package/dist/resolve/__tests__/phase.test.js.map +1 -0
  119. package/dist/resolve/defaults.d.ts +51 -0
  120. package/dist/resolve/defaults.d.ts.map +1 -0
  121. package/dist/resolve/defaults.js +109 -0
  122. package/dist/resolve/defaults.js.map +1 -0
  123. package/dist/resolve/order.d.ts +73 -0
  124. package/dist/resolve/order.d.ts.map +1 -0
  125. package/dist/resolve/order.js +208 -0
  126. package/dist/resolve/order.js.map +1 -0
  127. package/dist/resolve/phase.d.ts +57 -0
  128. package/dist/resolve/phase.d.ts.map +1 -0
  129. package/dist/resolve/phase.js +60 -0
  130. package/dist/resolve/phase.js.map +1 -0
  131. package/dist/scaffold/__tests__/scaffold.test.js +8 -2
  132. package/dist/scaffold/__tests__/scaffold.test.js.map +1 -1
  133. package/dist/scaffold/__tests__/settings-merge.test.js +95 -0
  134. package/dist/scaffold/__tests__/settings-merge.test.js.map +1 -1
  135. package/dist/scaffold/incremental.d.ts.map +1 -1
  136. package/dist/scaffold/incremental.js +13 -2
  137. package/dist/scaffold/incremental.js.map +1 -1
  138. package/dist/visualizer/__tests__/brain.test.d.ts +2 -0
  139. package/dist/visualizer/__tests__/brain.test.d.ts.map +1 -0
  140. package/dist/visualizer/__tests__/brain.test.js +138 -0
  141. package/dist/visualizer/__tests__/brain.test.js.map +1 -0
  142. package/dist/visualizer/__tests__/topology-brain-link.test.d.ts +2 -0
  143. package/dist/visualizer/__tests__/topology-brain-link.test.d.ts.map +1 -0
  144. package/dist/visualizer/__tests__/topology-brain-link.test.js +56 -0
  145. package/dist/visualizer/__tests__/topology-brain-link.test.js.map +1 -0
  146. package/dist/visualizer/brain.d.ts +97 -0
  147. package/dist/visualizer/brain.d.ts.map +1 -0
  148. package/dist/visualizer/brain.js +473 -0
  149. package/dist/visualizer/brain.js.map +1 -0
  150. package/dist/visualizer/index.d.ts +19 -1
  151. package/dist/visualizer/index.d.ts.map +1 -1
  152. package/dist/visualizer/index.js +26 -11
  153. package/dist/visualizer/index.js.map +1 -1
  154. package/examples/code-review.at +32 -4
  155. package/examples/company-brain-team.at +241 -0
  156. package/examples/company-brain.at +104 -0
  157. package/examples/data-processing.at +33 -4
  158. package/examples/logos/gmail.svg +1 -0
  159. package/examples/logos/slack.svg +1 -0
  160. package/package.json +1 -1
  161. package/spec/grammar.md +55 -3
  162. package/spec/validation.md +103 -35
@@ -0,0 +1,81 @@
1
+ /**
2
+ * An unphased agent must sort the same way on every target.
3
+ *
4
+ * Regression: six consumers each picked their own sentinel — MAX_SAFE_INTEGER
5
+ * in claude-code and claude-workflow, 999 in gemini-cli, 0 in openclaw, kiro,
6
+ * mermaid and the markdown exporter. So the same topology put an unphased
7
+ * agent LAST on three targets and FIRST on three others. That is exactly the
8
+ * failure "write once, deploy to any platform" exists to prevent, and nothing
9
+ * compared them.
10
+ */
11
+ import { describe, it, expect } from "vitest";
12
+ import { readFileSync, readdirSync } from "node:fs";
13
+ import { resolve, dirname, join } from "node:path";
14
+ import { fileURLToPath } from "node:url";
15
+ import { parse } from "../../parser/index.js";
16
+ import { phaseOf, byPhase, UNPHASED } from "../phase.js";
17
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), "../../..");
18
+ const MIXED = `topology t : [pipeline] {
19
+ meta {
20
+ version: "1.0.0"
21
+ description: "one phased agent, one not"
22
+ }
23
+ agent early {
24
+ model: sonnet
25
+ description: "e"
26
+ phase: 1
27
+ }
28
+ agent unphased {
29
+ model: sonnet
30
+ description: "u"
31
+ }
32
+ action i {
33
+ kind: inline
34
+ description: "in"
35
+ }
36
+ flow { i -> early -> unphased }
37
+ }`;
38
+ describe("phase resolution is defined in exactly one place", () => {
39
+ it("gives an unphased agent a key no real declaration can reach", () => {
40
+ // 999 collides with `phase: 999`; 0 collides with `phase: 0`. This cannot
41
+ // collide, because `phase` is parseFloat'd from a source literal.
42
+ expect(UNPHASED).toBe(Number.MAX_SAFE_INTEGER);
43
+ });
44
+ it("trails unphased agents rather than leading them", () => {
45
+ const agents = parse(MIXED).nodes.filter((n) => n.type === "agent");
46
+ const sorted = [...agents].sort(byPhase).map((a) => a.id);
47
+ expect(sorted).toEqual(["early", "unphased"]);
48
+ });
49
+ it("leaves declaration order intact when nobody declares a phase", () => {
50
+ const src = MIXED.replace(" phase: 1\n", "");
51
+ const agents = parse(src).nodes.filter((n) => n.type === "agent");
52
+ expect(agents.every((a) => phaseOf(a) === UNPHASED)).toBe(true);
53
+ expect([...agents].sort(byPhase).map((a) => a.id)).toEqual(["early", "unphased"]);
54
+ });
55
+ it("no consumer defines its own phase fallback", () => {
56
+ // The guard that actually prevents the regression: grep the source. A new
57
+ // `agent.phase ?? <n>` anywhere reintroduces the divergence silently.
58
+ const offenders = [];
59
+ const walk = (dir) => {
60
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
61
+ const p = join(dir, entry.name);
62
+ if (entry.isDirectory()) {
63
+ if (entry.name === "__tests__" || entry.name === "node_modules")
64
+ continue;
65
+ walk(p);
66
+ continue;
67
+ }
68
+ if (!entry.name.endsWith(".ts"))
69
+ continue;
70
+ if (p.endsWith(join("resolve", "phase.ts")))
71
+ continue;
72
+ const text = readFileSync(p, "utf8");
73
+ if (/\bphase\s*\?\?\s*/.test(text))
74
+ offenders.push(p.slice(root.length + 1));
75
+ }
76
+ };
77
+ walk(join(root, "src"));
78
+ expect(offenders, `these define their own phase fallback instead of using resolve/phase.ts: ${offenders.join(", ")}`).toEqual([]);
79
+ });
80
+ });
81
+ //# sourceMappingURL=phase.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"phase.test.js","sourceRoot":"","sources":["../../../src/resolve/__tests__/phase.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Defaults resolver — the first code in the package that reads the spec's
3
+ * defaults table (`spec/grammar.md` §7).
4
+ *
5
+ * WHY THIS EXISTS
6
+ * ---------------
7
+ * The parser writes a field only when it is present in the source, so every
8
+ * optional field on a node arrives `undefined`. Until now each binding filled
9
+ * the blanks itself, and they disagree: `agent.permissions` defaults to
10
+ * `"supervised"` in `bindings/codex.ts`, `"auto"` in `bindings/openclaw.ts`,
11
+ * while the spec says `autonomous`. Interpreted mode cannot tolerate that —
12
+ * an execution brief must state one resolved value and be able to name where
13
+ * it came from.
14
+ *
15
+ * WHAT THIS DOES NOT DO
16
+ * ---------------------
17
+ * It does not mutate the input and no binding consumes it. Wiring it into the
18
+ * bindings would silently change their output for three targets, which is a
19
+ * separate, deliberate change. This resolver serves `agentopology plan`.
20
+ *
21
+ * Only defaults that are a CONCRETE VALUE are applied. Table rows whose default
22
+ * is "-- (none)" stay `undefined`, because absence is their meaning.
23
+ *
24
+ * @module
25
+ */
26
+ import type { TopologyAST } from "../parser/ast.js";
27
+ /** One resolved field, and the fact that it was not authored. */
28
+ export interface ResolvedDefault {
29
+ /** Node id, or `"<topology>"` for topology-level fields. */
30
+ node: string;
31
+ /** Field name as it appears in the grammar (kebab-case). */
32
+ field: string;
33
+ /** The value the spec says to use. */
34
+ value: unknown;
35
+ }
36
+ /** Result of resolving defaults: a new AST plus the list of what was filled. */
37
+ export interface DefaultsResolution {
38
+ ast: TopologyAST;
39
+ applied: ResolvedDefault[];
40
+ }
41
+ /**
42
+ * Fill every omitted optional field that the spec gives a concrete default.
43
+ *
44
+ * Returns a NEW ast — the input is not mutated — plus the list of fields that
45
+ * were filled, so a caller can report which values the author never wrote.
46
+ *
47
+ * @param ast - A parsed topology.
48
+ * @returns The resolved AST and the record of what was applied.
49
+ */
50
+ export declare function resolveDefaults(ast: TopologyAST): DefaultsResolution;
51
+ //# sourceMappingURL=defaults.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/resolve/defaults.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAA4C,MAAM,kBAAkB,CAAC;AAE9F,iEAAiE;AACjE,MAAM,WAAW,eAAe;IAC9B,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,KAAK,EAAE,MAAM,CAAC;IACd,sCAAsC;IACtC,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,gFAAgF;AAChF,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,WAAW,CAAC;IACjB,OAAO,EAAE,eAAe,EAAE,CAAC;CAC5B;AAyDD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,WAAW,GAAG,kBAAkB,CA4BpE"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Defaults resolver — the first code in the package that reads the spec's
3
+ * defaults table (`spec/grammar.md` §7).
4
+ *
5
+ * WHY THIS EXISTS
6
+ * ---------------
7
+ * The parser writes a field only when it is present in the source, so every
8
+ * optional field on a node arrives `undefined`. Until now each binding filled
9
+ * the blanks itself, and they disagree: `agent.permissions` defaults to
10
+ * `"supervised"` in `bindings/codex.ts`, `"auto"` in `bindings/openclaw.ts`,
11
+ * while the spec says `autonomous`. Interpreted mode cannot tolerate that —
12
+ * an execution brief must state one resolved value and be able to name where
13
+ * it came from.
14
+ *
15
+ * WHAT THIS DOES NOT DO
16
+ * ---------------------
17
+ * It does not mutate the input and no binding consumes it. Wiring it into the
18
+ * bindings would silently change their output for three targets, which is a
19
+ * separate, deliberate change. This resolver serves `agentopology plan`.
20
+ *
21
+ * Only defaults that are a CONCRETE VALUE are applied. Table rows whose default
22
+ * is "-- (none)" stay `undefined`, because absence is their meaning.
23
+ *
24
+ * @module
25
+ */
26
+ /**
27
+ * Concrete agent defaults from `spec/grammar.md` §7. Keys are AST field names;
28
+ * `grammarName` is the kebab-case spelling used in `.at` source and in reports.
29
+ */
30
+ const AGENT_DEFAULTS = [
31
+ { key: "permissions", grammarName: "permissions", value: "autonomous" },
32
+ { key: "disallowedTools", grammarName: "disallowed-tools", value: [] },
33
+ { key: "reads", grammarName: "reads", value: [] },
34
+ { key: "writes", grammarName: "writes", value: [] },
35
+ { key: "outputs", grammarName: "outputs", value: {} },
36
+ { key: "retry", grammarName: "retry", value: 0 },
37
+ { key: "invocation", grammarName: "invocation", value: "auto" },
38
+ { key: "behavior", grammarName: "behavior", value: "blocking" },
39
+ { key: "memory", grammarName: "memory", value: [] },
40
+ { key: "skills", grammarName: "skills", value: [] },
41
+ { key: "mcpServers", grammarName: "mcp-servers", value: [] },
42
+ { key: "background", grammarName: "background", value: false },
43
+ { key: "fallbackChain", grammarName: "fallback-chain", value: [] },
44
+ ];
45
+ const GATE_DEFAULTS = [
46
+ { key: "checks", grammarName: "checks", value: [] },
47
+ { key: "retry", grammarName: "retry", value: 0 },
48
+ { key: "onFail", grammarName: "on-fail", value: "halt" },
49
+ { key: "behavior", grammarName: "behavior", value: "blocking" },
50
+ ];
51
+ const ACTION_DEFAULTS = [
52
+ { key: "commands", grammarName: "commands", value: [] },
53
+ ];
54
+ /** Deep-ish clone that is enough for an AST of plain data. */
55
+ function cloneValue(v) {
56
+ if (Array.isArray(v))
57
+ return [...v];
58
+ if (v && typeof v === "object")
59
+ return { ...v };
60
+ return v;
61
+ }
62
+ function applyTo(node, id, table, applied) {
63
+ for (const { key, grammarName, value } of table) {
64
+ if (node[key] === undefined) {
65
+ node[key] = cloneValue(value);
66
+ // Clone for the REPORT too. Pushing `value` handed the caller a reference
67
+ // to the module-level table, so one mutation of a reported value poisoned
68
+ // the default for every later topology in the process.
69
+ applied.push({ node: id, field: grammarName, value: cloneValue(value) });
70
+ }
71
+ }
72
+ }
73
+ /**
74
+ * Fill every omitted optional field that the spec gives a concrete default.
75
+ *
76
+ * Returns a NEW ast — the input is not mutated — plus the list of fields that
77
+ * were filled, so a caller can report which values the author never wrote.
78
+ *
79
+ * @param ast - A parsed topology.
80
+ * @returns The resolved AST and the record of what was applied.
81
+ */
82
+ export function resolveDefaults(ast) {
83
+ const applied = [];
84
+ const nodes = ast.nodes.map((n) => {
85
+ const copy = { ...n };
86
+ switch (n.type) {
87
+ case "agent":
88
+ applyTo(copy, n.id, AGENT_DEFAULTS, applied);
89
+ break;
90
+ case "gate":
91
+ applyTo(copy, n.id, GATE_DEFAULTS, applied);
92
+ break;
93
+ case "action":
94
+ applyTo(copy, n.id, ACTION_DEFAULTS, applied);
95
+ break;
96
+ case "orchestrator":
97
+ if (copy.outputs === undefined) {
98
+ copy.outputs = {};
99
+ applied.push({ node: n.id, field: "outputs", value: {} });
100
+ }
101
+ break;
102
+ default:
103
+ break;
104
+ }
105
+ return copy;
106
+ });
107
+ return { ast: { ...ast, nodes }, applied };
108
+ }
109
+ //# sourceMappingURL=defaults.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaults.js","sourceRoot":"","sources":["../../src/resolve/defaults.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Execution order resolver.
3
+ *
4
+ * WHY THIS EXISTS
5
+ * ---------------
6
+ * `computeLayers` in `../analyzer` is a correct Kahn topological rank, but it
7
+ * cannot be used directly for execution order. Gates bind to the graph through
8
+ * `after:` / `before:` fields, never through edges, so they have in-degree 0
9
+ * and Kahn ranks them as SOURCES. Measured on `examples/code-review.at`, the
10
+ * gate `human-approval` lands in layer 0 next to the entry action.
11
+ *
12
+ * So: rank the flow-participating nodes only, then splice gates back in at the
13
+ * position their `after` / `before` actually names.
14
+ *
15
+ * The orchestrator is deliberately NOT a step. In interpreted mode the host
16
+ * coding agent IS the orchestrator — it does not spawn one.
17
+ *
18
+ * @module
19
+ */
20
+ import type { TopologyAST, EdgeDef } from "../parser/ast.js";
21
+ /** What the host does at one step. */
22
+ export type StepKind = "spawn" | "gate" | "action" | "human" | "group";
23
+ /** One step of the resolved execution order. */
24
+ export interface OrderStep {
25
+ /** 1-based position. */
26
+ index: number;
27
+ kind: StepKind;
28
+ /** Node ids. More than one means they run in parallel, mutually blind. */
29
+ ids: string[];
30
+ /** Graph rank, or `null` for a gate (spliced by position, never ranked). */
31
+ depth: number | null;
32
+ /**
33
+ * True when the ids are mutually EXCLUSIVE branches, not a parallel fan-out:
34
+ * every one is reached only by a conditional edge from the same source.
35
+ * Exactly one runs.
36
+ *
37
+ * Without this the renderer called them "parallel" and the brief marked them
38
+ * mutually blind and told the host to dispatch all of them in one message —
39
+ * running every branch of a decision instead of taking one.
40
+ */
41
+ exclusive: boolean;
42
+ /** For an exclusive step, the condition that selects each id. */
43
+ branchOn?: Array<{
44
+ id: string;
45
+ condition: string;
46
+ }>;
47
+ }
48
+ /** A back-edge with its iteration budget. */
49
+ export interface LoopInfo {
50
+ from: string;
51
+ to: string;
52
+ condition: string | null;
53
+ /** `[max N]` — the number of TRAVERSALS allowed, so the target runs N+1 times. */
54
+ budget: number | null;
55
+ }
56
+ export interface ResolvedOrder {
57
+ steps: OrderStep[];
58
+ loops: LoopInfo[];
59
+ /** Nodes Kahn could not reach — cycle members. `computeLayers` gives depth -1. */
60
+ unreachable: string[];
61
+ /** The orchestrator's id, if declared. It is the host, not a step. */
62
+ orchestrator: string | null;
63
+ }
64
+ /**
65
+ * Resolve the order in which a host agent should enact a topology.
66
+ *
67
+ * @param ast - A parsed topology. Order does not depend on defaults resolution.
68
+ * @returns Ordered steps, loop budgets, unreachable nodes, and the orchestrator id.
69
+ */
70
+ export declare function resolveOrder(ast: TopologyAST): ResolvedOrder;
71
+ /** All outbound edges from a node, in declaration order. */
72
+ export declare function edgesFrom(ast: TopologyAST, id: string): EdgeDef[];
73
+ //# sourceMappingURL=order.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"order.d.ts","sourceRoot":"","sources":["../../src/resolve/order.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAqB,MAAM,kBAAkB,CAAC;AAGhF,sCAAsC;AACtC,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC;AAEvE,gDAAgD;AAChD,MAAM,WAAW,SAAS;IACxB,wBAAwB;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,CAAC;IACf,0EAA0E;IAC1E,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,4EAA4E;IAC5E,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB;;;;;;;;OAQG;IACH,SAAS,EAAE,OAAO,CAAC;IACnB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACrD;AAED,6CAA6C;AAC7C,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,kFAAkF;IAClF,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,kFAAkF;IAClF,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,sEAAsE;IACtE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAkDD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,WAAW,GAAG,aAAa,CAyI5D;AAED,4DAA4D;AAC5D,wBAAgB,SAAS,CAAC,GAAG,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,EAAE,CAEjE"}
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Execution order resolver.
3
+ *
4
+ * WHY THIS EXISTS
5
+ * ---------------
6
+ * `computeLayers` in `../analyzer` is a correct Kahn topological rank, but it
7
+ * cannot be used directly for execution order. Gates bind to the graph through
8
+ * `after:` / `before:` fields, never through edges, so they have in-degree 0
9
+ * and Kahn ranks them as SOURCES. Measured on `examples/code-review.at`, the
10
+ * gate `human-approval` lands in layer 0 next to the entry action.
11
+ *
12
+ * So: rank the flow-participating nodes only, then splice gates back in at the
13
+ * position their `after` / `before` actually names.
14
+ *
15
+ * The orchestrator is deliberately NOT a step. In interpreted mode the host
16
+ * coding agent IS the orchestrator — it does not spawn one.
17
+ *
18
+ * @module
19
+ */
20
+ import { computeLayers } from "../analyzer/index.js";
21
+ /**
22
+ * True back-edges, found by DFS over the FULL edge set.
23
+ *
24
+ * `findBackEdges` in the validator cannot be reused here: it strips every edge
25
+ * carrying `[max N]` first, because its job is to find loops the author has NOT
26
+ * acknowledged (that is V6). We need the opposite — the acknowledged ones too.
27
+ *
28
+ * And `computeLayers` strips them as well, so a plain FORWARD edge that happens
29
+ * to carry a bound both corrupts the ranking and gets reported as a loop.
30
+ * Classifying first, then ranking with only the real back-edges removed, fixes
31
+ * both.
32
+ */
33
+ function trueBackEdges(edges, nodeIds) {
34
+ const out = new Map();
35
+ for (const e of edges) {
36
+ if (e.isError)
37
+ continue;
38
+ if (!nodeIds.has(e.from) || !nodeIds.has(e.to))
39
+ continue;
40
+ if (!out.has(e.from))
41
+ out.set(e.from, []);
42
+ out.get(e.from).push(e.to);
43
+ }
44
+ const WHITE = 0, GREY = 1, BLACK = 2;
45
+ const colour = new Map([...nodeIds].map((id) => [id, WHITE]));
46
+ const back = new Set();
47
+ const visit = (id) => {
48
+ colour.set(id, GREY);
49
+ for (const to of out.get(id) ?? []) {
50
+ const c = colour.get(to) ?? WHITE;
51
+ // An edge into a node still on the DFS stack closes a cycle.
52
+ if (c === GREY)
53
+ back.add(`${id} ${to}`);
54
+ else if (c === WHITE)
55
+ visit(to);
56
+ }
57
+ colour.set(id, BLACK);
58
+ };
59
+ for (const id of nodeIds)
60
+ if (colour.get(id) === WHITE)
61
+ visit(id);
62
+ return back;
63
+ }
64
+ const KIND_BY_TYPE = {
65
+ agent: "spawn",
66
+ action: "action",
67
+ human: "human",
68
+ group: "group",
69
+ gate: "gate",
70
+ };
71
+ /**
72
+ * Resolve the order in which a host agent should enact a topology.
73
+ *
74
+ * @param ast - A parsed topology. Order does not depend on defaults resolution.
75
+ * @returns Ordered steps, loop budgets, unreachable nodes, and the orchestrator id.
76
+ */
77
+ export function resolveOrder(ast) {
78
+ const byId = new Map(ast.nodes.map((n) => [n.id, n]));
79
+ const orchestrator = ast.nodes.find((n) => n.type === "orchestrator")?.id ?? null;
80
+ const gates = ast.nodes.filter((n) => n.type === "gate");
81
+ const gateIds = new Set(gates.map((g) => g.id));
82
+ // Rank only what actually participates in the flow graph. Gates are excluded
83
+ // because they have no edges; the orchestrator because it is the host.
84
+ const flowIds = new Set(ast.nodes.filter((n) => !gateIds.has(n.id) && n.type !== "orchestrator").map((n) => n.id));
85
+ // Classify first: rank with only the REAL back-edges removed, so a forward
86
+ // edge carrying `[max N]` still advances the depth of its target.
87
+ const backKeys = trueBackEdges(ast.edges, flowIds);
88
+ const forwardEdges = ast.edges.filter((e) => !backKeys.has(`${e.from} ${e.to}`));
89
+ const layers = computeLayers(
90
+ // computeLayers strips `maxIterations` itself, so clear it on the edges we
91
+ // have already established are forward — otherwise they vanish again.
92
+ forwardEdges.map((e) => (e.maxIterations ? { ...e, maxIterations: null } : e)), flowIds);
93
+ // Build the ranked spine, splitting each layer by node kind so a step is
94
+ // never a mix of "spawn three agents" and "run a shell action".
95
+ const spine = [];
96
+ const unreachable = [];
97
+ for (const layer of layers) {
98
+ if (layer.depth === -1) {
99
+ unreachable.push(...layer.nodes);
100
+ continue;
101
+ }
102
+ const grouped = new Map();
103
+ for (const id of layer.nodes) {
104
+ const node = byId.get(id);
105
+ if (!node)
106
+ continue;
107
+ const kind = KIND_BY_TYPE[node.type] ?? "spawn";
108
+ if (!grouped.has(kind))
109
+ grouped.set(kind, []);
110
+ grouped.get(kind).push(id);
111
+ }
112
+ // Deterministic kind order within a layer: work first, then human gates.
113
+ for (const kind of ["action", "spawn", "group", "human"]) {
114
+ const ids = grouped.get(kind);
115
+ if (ids && ids.length)
116
+ spine.push({ kind, ids, depth: layer.depth });
117
+ }
118
+ }
119
+ // Splice gates. `after: X` inserts immediately after the step containing X.
120
+ // `before: Y` inserts immediately before the step containing Y. A gate with
121
+ // both prefers `after`, since that is when its check can actually run.
122
+ const withGates = [...spine];
123
+ // Splice in declaration order, tracking how many gates already landed on each
124
+ // anchor. Without the offset each new gate inserted at the same index and
125
+ // pushed its predecessor down, so two gates after the same agent ran in
126
+ // reverse declaration order.
127
+ const placedPerAnchor = new Map();
128
+ // A gate may anchor to ANOTHER gate. Splicing in declaration order meant a
129
+ // gate whose anchor was declared later found nothing, fell through to "runs
130
+ // last", and silently skipped past the step it was meant to guard. Order the
131
+ // gates so every anchor is already placed: repeat until no progress, then
132
+ // append whatever still cannot resolve.
133
+ const placedIds = new Set();
134
+ const pending = [...gates];
135
+ const ordered = [];
136
+ for (let pass = 0; pass < gates.length + 1 && pending.length; pass++) {
137
+ let moved = false;
138
+ for (let i = 0; i < pending.length;) {
139
+ const g = pending[i];
140
+ const anchors = [g.after, g.before].filter((a) => !!a);
141
+ const waitingOnGate = anchors.some((a) => gateIds.has(a) && !placedIds.has(a));
142
+ if (waitingOnGate) {
143
+ i++;
144
+ continue;
145
+ }
146
+ ordered.push(g);
147
+ placedIds.add(g.id);
148
+ pending.splice(i, 1);
149
+ moved = true;
150
+ }
151
+ if (!moved)
152
+ break; // a cycle among gate anchors — append them as-is
153
+ }
154
+ ordered.push(...pending);
155
+ for (const gate of ordered) {
156
+ let at = -1;
157
+ if (gate.after) {
158
+ const i = withGates.findIndex((s) => s.ids.includes(gate.after));
159
+ if (i >= 0) {
160
+ const already = placedPerAnchor.get(gate.after) ?? 0;
161
+ at = i + 1 + already;
162
+ placedPerAnchor.set(gate.after, already + 1);
163
+ }
164
+ }
165
+ if (at === -1 && gate.before) {
166
+ const i = withGates.findIndex((s) => s.ids.includes(gate.before));
167
+ if (i >= 0)
168
+ at = i;
169
+ }
170
+ // A gate anchored to nothing resolvable runs last — visible, not dropped.
171
+ if (at === -1)
172
+ at = withGates.length;
173
+ withGates.splice(at, 0, { kind: "gate", ids: [gate.id], depth: null });
174
+ }
175
+ // Classify each multi-node step: parallel fan-out, or exclusive branch?
176
+ const steps = withGates.map((s, i) => {
177
+ const base = { index: i + 1, ...s, exclusive: false };
178
+ if (s.ids.length < 2)
179
+ return base;
180
+ const inbound = s.ids.map((id) => ast.edges.filter((e) => e.to === id && !e.isError));
181
+ // Every id reached ONLY by conditional edges, and every one of those edges
182
+ // from the same single source → this is one decision, not a fan-out.
183
+ const allConditional = inbound.every((es) => es.length > 0 && es.every((e) => !!e.condition));
184
+ const sources = new Set(inbound.flat().map((e) => e.from));
185
+ if (!allConditional || sources.size !== 1)
186
+ return base;
187
+ base.exclusive = true;
188
+ base.branchOn = s.ids.map((id) => ({
189
+ id,
190
+ condition: ast.edges.find((e) => e.to === id && e.condition)?.condition ?? "",
191
+ }));
192
+ return base;
193
+ });
194
+ const loops = ast.edges
195
+ .filter((e) => backKeys.has(`${e.from} ${e.to}`))
196
+ .map((e) => ({
197
+ from: e.from,
198
+ to: e.to,
199
+ condition: e.condition,
200
+ budget: e.maxIterations ?? null,
201
+ }));
202
+ return { steps, loops, unreachable, orchestrator };
203
+ }
204
+ /** All outbound edges from a node, in declaration order. */
205
+ export function edgesFrom(ast, id) {
206
+ return ast.edges.filter((e) => e.from === id);
207
+ }
208
+ //# sourceMappingURL=order.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"order.js","sourceRoot":"","sources":["../../src/resolve/order.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * The single answer to "where does an agent with no declared `phase` go?"
3
+ *
4
+ * THE BUG THIS FIXES
5
+ * ------------------
6
+ * Six consumers each picked their own sentinel, so the same topology ordered
7
+ * an unphased agent differently on every target:
8
+ *
9
+ * claude-code, claude-workflow → Number.MAX_SAFE_INTEGER → runs LAST
10
+ * gemini-cli → 999 → runs near-last
11
+ * openclaw, kiro, mermaid → 0 → runs FIRST
12
+ *
13
+ * An agent that runs first on three targets and last on three others is
14
+ * precisely the failure "write once, deploy to any platform" exists to prevent.
15
+ * Found by an audit topology run through `agentopology plan`.
16
+ *
17
+ * WHY TRAILING, NOT LEADING
18
+ * -------------------------
19
+ * `spec/grammar.md` §7 gives `agent.phase` the default "-- (none, unordered)",
20
+ * so there is no declared answer — a sort simply needs one. Trailing is the
21
+ * safer reading: an author who phases *some* agents is expressing "these come
22
+ * first, in this order", and the ones they did not mention should not leap
23
+ * ahead of them. A stable sort then preserves declaration order among the
24
+ * unphased, which is the only ordering information they carry.
25
+ *
26
+ * When NO agent declares a phase, every agent takes the same sentinel and the
27
+ * stable sort leaves declaration order intact — the same result all six
28
+ * sentinels used to give, which is why this went unnoticed.
29
+ *
30
+ * @module
31
+ */
32
+ import type { AgentNode } from "../parser/ast.js";
33
+ /**
34
+ * Sort key for an agent with no declared `phase`.
35
+ *
36
+ * Deliberately `Number.MAX_SAFE_INTEGER` rather than a smaller magic number:
37
+ * `999` collides with a legitimately declared `phase: 999`, and `0` collides
38
+ * with `phase: 0`. This one cannot be reached by a real declaration, since
39
+ * `phase` is parsed with `parseFloat` from a source literal.
40
+ */
41
+ export declare const UNPHASED: number;
42
+ /**
43
+ * The ordering value for an agent — its declared `phase`, or {@link UNPHASED}.
44
+ *
45
+ * Every consumer that sorts by phase must use this. Sorting by
46
+ * `agent.phase ?? <anything else>` reintroduces the divergence.
47
+ *
48
+ * @param agent - Any agent node.
49
+ * @returns The numeric sort key.
50
+ */
51
+ export declare function phaseOf(agent: Pick<AgentNode, "phase">): number;
52
+ /**
53
+ * Comparator for `Array.prototype.sort`, stable on ties so declaration order
54
+ * survives among agents sharing a phase.
55
+ */
56
+ export declare function byPhase(a: Pick<AgentNode, "phase">, b: Pick<AgentNode, "phase">): number;
57
+ //# sourceMappingURL=phase.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"phase.d.ts","sourceRoot":"","sources":["../../src/resolve/phase.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElD;;;;;;;GAOG;AACH,eAAO,MAAM,QAAQ,QAA0B,CAAC;AAEhD;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,GAAG,MAAM,CAE/D;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,GAAG,MAAM,CAExF"}