u-foo 1.8.5 → 1.8.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/agent/activityDetector.js +33 -0
- package/src/agent/claudeSessionFiles.js +127 -0
- package/src/agent/launcher.js +54 -29
- package/src/bus/subscriber.js +16 -3
- package/src/chat/commandExecutor.js +0 -1
- package/src/chat/commands.js +10 -2
- package/src/chat/daemonCoordinator.js +1 -0
- package/src/chat/daemonMessageRouter.js +27 -16
- package/src/chat/daemonReconnect.js +6 -3
- package/src/chat/index.js +1 -0
- package/src/chat/inputMath.js +175 -38
- package/src/chat/inputSubmitHandler.js +10 -5
- package/src/chat/settingsController.js +3 -1
- package/src/chat/text.js +6 -0
- package/src/code/agent.js +27 -6
- package/src/code/nativeRunner.js +8 -4
- package/src/code/prompts/actions.js +21 -0
- package/src/code/prompts/efficiency.js +18 -0
- package/src/code/prompts/environment.js +50 -0
- package/src/code/prompts/identity.js +20 -0
- package/src/code/prompts/index.js +103 -0
- package/src/code/prompts/safety.js +11 -0
- package/src/code/prompts/sections.js +60 -0
- package/src/code/prompts/system.js +16 -0
- package/src/code/prompts/tasks.js +17 -0
- package/src/code/prompts/toolDescriptions/bash.js +21 -0
- package/src/code/prompts/toolDescriptions/edit.js +16 -0
- package/src/code/prompts/toolDescriptions/read.js +17 -0
- package/src/code/prompts/toolDescriptions/write.js +16 -0
- package/src/code/prompts/ufoo.js +21 -0
- package/src/daemon/groupOrchestrator.js +97 -7
- package/src/daemon/index.js +53 -14
- package/src/daemon/nicknameScope.js +80 -0
- package/src/daemon/ops.js +19 -6
- package/src/daemon/soloBootstrap.js +15 -2
- package/src/group/promptProfiles.js +32 -0
- package/templates/groups/build-ultra.json +219 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
function asTrimmedString(value) {
|
|
7
|
+
if (typeof value !== "string") return "";
|
|
8
|
+
return value.trim();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function normalizeNicknameSegment(value = "", fallback = "agent") {
|
|
12
|
+
const normalized = asTrimmedString(value)
|
|
13
|
+
.toLowerCase()
|
|
14
|
+
.replace(/[^a-z0-9_-]+/g, "-")
|
|
15
|
+
.replace(/-+/g, "-")
|
|
16
|
+
.replace(/^-+|-+$/g, "");
|
|
17
|
+
return normalized || fallback;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalizeAutoNicknamePrefix(agentType = "") {
|
|
21
|
+
const normalized = asTrimmedString(agentType).toLowerCase();
|
|
22
|
+
if (normalized === "claude" || normalized === "claude-code") return "claude";
|
|
23
|
+
if (normalized === "codex") return "codex";
|
|
24
|
+
if (normalized === "ufoo" || normalized === "ucode" || normalized === "ufoo-code") return "ucode";
|
|
25
|
+
return "";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function buildProjectNicknamePrefix(projectRoot) {
|
|
29
|
+
const resolvedRoot = asTrimmedString(projectRoot) || process.cwd();
|
|
30
|
+
let baseName = "";
|
|
31
|
+
try {
|
|
32
|
+
const realRoot = fs.realpathSync.native
|
|
33
|
+
? fs.realpathSync.native(resolvedRoot)
|
|
34
|
+
: fs.realpathSync(resolvedRoot);
|
|
35
|
+
baseName = path.basename(realRoot);
|
|
36
|
+
} catch {
|
|
37
|
+
baseName = path.basename(path.resolve(resolvedRoot));
|
|
38
|
+
}
|
|
39
|
+
return normalizeNicknameSegment(baseName, "project");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isAutoGeneratedNickname(nickname = "", agentType = "") {
|
|
43
|
+
const normalized = normalizeNicknameSegment(nickname, "");
|
|
44
|
+
const match = normalized.match(/^([a-z0-9]+)-([a-z0-9_-]+)$/);
|
|
45
|
+
if (!match) return false;
|
|
46
|
+
|
|
47
|
+
const nicknamePrefix = match[1];
|
|
48
|
+
const suffix = match[2];
|
|
49
|
+
const knownPrefixes = new Set(["claude", "codex", "ucode"]);
|
|
50
|
+
const agentPrefix = normalizeAutoNicknamePrefix(agentType);
|
|
51
|
+
if (agentPrefix) knownPrefixes.add(agentPrefix);
|
|
52
|
+
if (!knownPrefixes.has(nicknamePrefix)) return false;
|
|
53
|
+
return /\d/.test(suffix);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function applyProjectNicknamePrefix(projectRoot, nickname = "", options = {}) {
|
|
57
|
+
const rawNickname = asTrimmedString(nickname);
|
|
58
|
+
if (!rawNickname) return "";
|
|
59
|
+
|
|
60
|
+
const projectPrefix = buildProjectNicknamePrefix(projectRoot);
|
|
61
|
+
const normalizedNickname = normalizeNicknameSegment(rawNickname, "");
|
|
62
|
+
if (!normalizedNickname) return "";
|
|
63
|
+
|
|
64
|
+
if (normalizedNickname === projectPrefix || normalizedNickname.startsWith(`${projectPrefix}-`)) {
|
|
65
|
+
return normalizedNickname;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (options.force !== true && isAutoGeneratedNickname(normalizedNickname, options.agentType || "")) {
|
|
69
|
+
return normalizedNickname;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return `${projectPrefix}-${normalizedNickname}`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = {
|
|
76
|
+
normalizeNicknameSegment,
|
|
77
|
+
buildProjectNicknamePrefix,
|
|
78
|
+
isAutoGeneratedNickname,
|
|
79
|
+
applyProjectNicknamePrefix,
|
|
80
|
+
};
|
package/src/daemon/ops.js
CHANGED
|
@@ -7,6 +7,7 @@ const { loadAgentsData, saveAgentsData } = require("../ufoo/agentsStore");
|
|
|
7
7
|
const { isAgentPidAlive, getTtyProcessInfo } = require("../bus/utils");
|
|
8
8
|
const { isITerm2 } = require("../terminal/detect");
|
|
9
9
|
const { createTerminalAdapterRouter } = require("../terminal/adapterRouter");
|
|
10
|
+
const { applyProjectNicknamePrefix } = require("./nicknameScope");
|
|
10
11
|
const {
|
|
11
12
|
createSession: createHostSession,
|
|
12
13
|
} = require("../terminal/adapters/hostAdapter");
|
|
@@ -126,6 +127,11 @@ function resolveAgentId(projectRoot, agentId) {
|
|
|
126
127
|
const entries = Object.entries(bus.agents || {});
|
|
127
128
|
const match = entries.find(([, meta]) => meta?.nickname === agentId);
|
|
128
129
|
if (match) return match[0];
|
|
130
|
+
const scopedNickname = applyProjectNicknamePrefix(projectRoot, agentId);
|
|
131
|
+
if (scopedNickname && scopedNickname !== agentId) {
|
|
132
|
+
const scopedMatch = entries.find(([, meta]) => meta?.nickname === scopedNickname);
|
|
133
|
+
if (scopedMatch) return scopedMatch[0];
|
|
134
|
+
}
|
|
129
135
|
const normalized = normalizeLaunchAgent(agentId);
|
|
130
136
|
const targetType = toBusAgentType(normalized) || agentId;
|
|
131
137
|
const candidates = entries
|
|
@@ -147,6 +153,7 @@ function markAgentInactive(projectRoot, agentId) {
|
|
|
147
153
|
data.agents[agentId] = {
|
|
148
154
|
...meta,
|
|
149
155
|
status: "inactive",
|
|
156
|
+
activity_state: "",
|
|
150
157
|
last_seen: new Date().toISOString(),
|
|
151
158
|
};
|
|
152
159
|
saveAgentsData(filePath, data);
|
|
@@ -842,6 +849,7 @@ async function launchAgent(projectRoot, agent, count = 1, nickname = "", process
|
|
|
842
849
|
const terminalApp = normalizeTerminalAppPreference(options.terminalApp);
|
|
843
850
|
const extraEnvObject = options.extraEnv && typeof options.extraEnv === "object" ? options.extraEnv : {};
|
|
844
851
|
const extraEnvPrefix = buildShellEnvPrefix(extraEnvObject);
|
|
852
|
+
const extraArgs = Array.isArray(options.extraArgs) ? options.extraArgs : [];
|
|
845
853
|
const normalizedAgent = normalizeLaunchAgent(agent);
|
|
846
854
|
if (!normalizedAgent) {
|
|
847
855
|
throw new Error(`unsupported agent type: ${agent}`);
|
|
@@ -894,7 +902,7 @@ async function launchAgent(projectRoot, agent, count = 1, nickname = "", process
|
|
|
894
902
|
const nick = count > 1 ? `${nickname || defaultNick}-${i + 1}` : (nickname || "");
|
|
895
903
|
if (useSeparateWindow) {
|
|
896
904
|
// eslint-disable-next-line no-await-in-loop
|
|
897
|
-
await spawnTmuxWindow(projectRoot, normalizedAgent, nick,
|
|
905
|
+
await spawnTmuxWindow(projectRoot, normalizedAgent, nick, extraArgs, extraEnvPrefix);
|
|
898
906
|
} else if (useGroupRightColumnLayout && paneTarget) {
|
|
899
907
|
const basePane = String(tmuxLayoutContext.basePane || paneTarget).trim() || paneTarget;
|
|
900
908
|
tmuxLayoutContext.basePane = basePane;
|
|
@@ -904,14 +912,14 @@ async function launchAgent(projectRoot, agent, count = 1, nickname = "", process
|
|
|
904
912
|
let splitResult;
|
|
905
913
|
try {
|
|
906
914
|
// eslint-disable-next-line no-await-in-loop
|
|
907
|
-
splitResult = await spawnTmuxPane(projectRoot, normalizedAgent, nick,
|
|
915
|
+
splitResult = await spawnTmuxPane(projectRoot, normalizedAgent, nick, extraArgs, extraEnvPrefix, splitTarget, {
|
|
908
916
|
orientation: splitOrientation,
|
|
909
917
|
capturePaneId: !rightColumnPane,
|
|
910
918
|
});
|
|
911
919
|
} catch {
|
|
912
920
|
// Fallback to new window when current pane target cannot be resolved.
|
|
913
921
|
// eslint-disable-next-line no-await-in-loop
|
|
914
|
-
await spawnTmuxWindow(projectRoot, normalizedAgent, nick,
|
|
922
|
+
await spawnTmuxWindow(projectRoot, normalizedAgent, nick, extraArgs, extraEnvPrefix);
|
|
915
923
|
continue;
|
|
916
924
|
}
|
|
917
925
|
if (!rightColumnPane && splitResult && splitResult.paneId) {
|
|
@@ -923,11 +931,11 @@ async function launchAgent(projectRoot, agent, count = 1, nickname = "", process
|
|
|
923
931
|
} else {
|
|
924
932
|
try {
|
|
925
933
|
// eslint-disable-next-line no-await-in-loop
|
|
926
|
-
await spawnTmuxPane(projectRoot, normalizedAgent, nick,
|
|
934
|
+
await spawnTmuxPane(projectRoot, normalizedAgent, nick, extraArgs, extraEnvPrefix, paneTarget);
|
|
927
935
|
} catch {
|
|
928
936
|
// Fallback to new window when current pane target cannot be resolved.
|
|
929
937
|
// eslint-disable-next-line no-await-in-loop
|
|
930
|
-
await spawnTmuxWindow(projectRoot, normalizedAgent, nick,
|
|
938
|
+
await spawnTmuxWindow(projectRoot, normalizedAgent, nick, extraArgs, extraEnvPrefix);
|
|
931
939
|
}
|
|
932
940
|
}
|
|
933
941
|
}
|
|
@@ -1009,10 +1017,15 @@ function collectRecoverableAgents(projectRoot, target = "") {
|
|
|
1009
1017
|
|
|
1010
1018
|
let targets = entries;
|
|
1011
1019
|
if (target) {
|
|
1020
|
+
const scopedTarget = applyProjectNicknamePrefix(projectRoot, target);
|
|
1012
1021
|
if (target.includes(":")) {
|
|
1013
1022
|
targets = entries.filter(([id]) => id === target);
|
|
1014
1023
|
} else {
|
|
1015
|
-
targets = entries.filter(([id, meta]) =>
|
|
1024
|
+
targets = entries.filter(([id, meta]) =>
|
|
1025
|
+
id === target
|
|
1026
|
+
|| (meta && meta.nickname === target)
|
|
1027
|
+
|| (scopedTarget && scopedTarget !== target && meta && meta.nickname === scopedTarget)
|
|
1028
|
+
);
|
|
1016
1029
|
}
|
|
1017
1030
|
}
|
|
1018
1031
|
|
|
@@ -5,6 +5,7 @@ const EventBus = require("../bus");
|
|
|
5
5
|
const { prepareUcodeBootstrap } = require("../agent/ucodeBootstrap");
|
|
6
6
|
const { isMetaActive } = require("../bus/utils");
|
|
7
7
|
const { getUfooPaths } = require("../ufoo/paths");
|
|
8
|
+
const { applyProjectNicknamePrefix } = require("./nicknameScope");
|
|
8
9
|
const { loadAgentsData, saveAgentsData } = require("../ufoo/agentsStore");
|
|
9
10
|
const {
|
|
10
11
|
loadPromptProfileRegistry,
|
|
@@ -194,13 +195,21 @@ function isLiveAgentMeta(meta) {
|
|
|
194
195
|
function resolveExistingAgent(projectRoot, target = "") {
|
|
195
196
|
const key = String(target || "").trim();
|
|
196
197
|
if (!key) return null;
|
|
198
|
+
const scopedKey = applyProjectNicknamePrefix(projectRoot, key);
|
|
197
199
|
const bus = loadBusMeta(projectRoot);
|
|
198
200
|
const agents = bus && bus.agents ? bus.agents : {};
|
|
199
201
|
if (isLiveAgentMeta(agents[key])) {
|
|
200
202
|
return { subscriberId: key, meta: agents[key] };
|
|
201
203
|
}
|
|
204
|
+
if (scopedKey && scopedKey !== key && isLiveAgentMeta(agents[scopedKey])) {
|
|
205
|
+
return { subscriberId: scopedKey, meta: agents[scopedKey] };
|
|
206
|
+
}
|
|
202
207
|
for (const [subscriberId, meta] of Object.entries(agents)) {
|
|
203
|
-
if (
|
|
208
|
+
if (
|
|
209
|
+
meta
|
|
210
|
+
&& (meta.nickname === key || (scopedKey && scopedKey !== key && meta.nickname === scopedKey))
|
|
211
|
+
&& isLiveAgentMeta(meta)
|
|
212
|
+
) {
|
|
204
213
|
return { subscriberId, meta };
|
|
205
214
|
}
|
|
206
215
|
}
|
|
@@ -232,7 +241,11 @@ function findOwningGroup(projectRoot, subscriberId = "") {
|
|
|
232
241
|
&& asTrimmedString(member.subscriber_id) === targetSubscriber
|
|
233
242
|
&& (member.status === "active" || member.status === "reused")
|
|
234
243
|
&& asTrimmedString(member.nickname)
|
|
235
|
-
&& (
|
|
244
|
+
&& (
|
|
245
|
+
!liveNickname
|
|
246
|
+
|| asTrimmedString(member.nickname) === liveNickname
|
|
247
|
+
|| asTrimmedString(member.runtime_nickname) === liveNickname
|
|
248
|
+
)
|
|
236
249
|
);
|
|
237
250
|
if (found) {
|
|
238
251
|
return {
|
|
@@ -352,6 +352,38 @@ const BUILTIN_PROFILES = [
|
|
|
352
352
|
"- Use `ufoo bus send <target-nickname> \"<message>\"` to deliver handoffs to other agents.",
|
|
353
353
|
].join("\n"),
|
|
354
354
|
},
|
|
355
|
+
{
|
|
356
|
+
id: "pmo-coordinator",
|
|
357
|
+
display_name: "PMO",
|
|
358
|
+
short_name: "PMO",
|
|
359
|
+
aliases: ["pmo"],
|
|
360
|
+
summary: "Coordinate execution across builders, track progress, unblock dependencies, and enforce delivery cadence.",
|
|
361
|
+
prompt: [
|
|
362
|
+
"You are the PMO coordinator for this ufoo group.",
|
|
363
|
+
"",
|
|
364
|
+
"Mission:",
|
|
365
|
+
"- Coordinate execution across multiple builders to maximize throughput and minimize idle time.",
|
|
366
|
+
"- Track progress, surface blockers early, enforce delivery cadence, and keep the team aligned on priorities.",
|
|
367
|
+
"",
|
|
368
|
+
"Boundaries:",
|
|
369
|
+
"- Do not make architectural or scope decisions — escalate to architect or scope challenger.",
|
|
370
|
+
"- Do not write production code.",
|
|
371
|
+
"- Do not reorder priorities without naming the tradeoff and notifying affected agents.",
|
|
372
|
+
"",
|
|
373
|
+
"Method:",
|
|
374
|
+
"- Assign slices to builders based on dependency order and current load.",
|
|
375
|
+
"- Monitor builder progress and proactively unblock stalled work.",
|
|
376
|
+
"- Maintain a clear view of what is done, in-flight, and blocked at all times.",
|
|
377
|
+
"- Enforce review gates — no slice ships without reviewer sign-off.",
|
|
378
|
+
"- Batch related changes when possible to reduce review churn.",
|
|
379
|
+
"",
|
|
380
|
+
"Handoff:",
|
|
381
|
+
"- Send execution-ready slices to builders with clear acceptance criteria.",
|
|
382
|
+
"- Send completed work to reviewer with context on what changed and why.",
|
|
383
|
+
"- Escalate blockers to architect or the human operator.",
|
|
384
|
+
"- Use `ufoo bus send <target-nickname> \"<message>\"` to deliver handoffs to other agents.",
|
|
385
|
+
].join("\n"),
|
|
386
|
+
},
|
|
355
387
|
{
|
|
356
388
|
id: "rapid-prototype",
|
|
357
389
|
display_name: "Prototype",
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"template": {
|
|
4
|
+
"id": "build-ultra",
|
|
5
|
+
"alias": "build-ultra",
|
|
6
|
+
"name": "Build Ultra"
|
|
7
|
+
},
|
|
8
|
+
"defaults": {
|
|
9
|
+
"launch_mode": "auto",
|
|
10
|
+
"start_timeout_ms": 15000
|
|
11
|
+
},
|
|
12
|
+
"agents": [
|
|
13
|
+
{
|
|
14
|
+
"id": "pmo",
|
|
15
|
+
"nickname": "pmo",
|
|
16
|
+
"type": "auto",
|
|
17
|
+
"role": "coordinate builders, track progress, enforce delivery cadence",
|
|
18
|
+
"prompt_profile": "pmo-coordinator",
|
|
19
|
+
"accept_from": [],
|
|
20
|
+
"report_to": [
|
|
21
|
+
"builder-1",
|
|
22
|
+
"builder-2",
|
|
23
|
+
"builder-3",
|
|
24
|
+
"builder-4",
|
|
25
|
+
"reviewer"
|
|
26
|
+
],
|
|
27
|
+
"startup_order": 1,
|
|
28
|
+
"depends_on": []
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "builder-1",
|
|
32
|
+
"nickname": "builder-1",
|
|
33
|
+
"type": "auto",
|
|
34
|
+
"role": "implement approved slices",
|
|
35
|
+
"prompt_profile": "implementation-lead",
|
|
36
|
+
"accept_from": [
|
|
37
|
+
"pmo",
|
|
38
|
+
"reviewer"
|
|
39
|
+
],
|
|
40
|
+
"report_to": [
|
|
41
|
+
"pmo",
|
|
42
|
+
"reviewer"
|
|
43
|
+
],
|
|
44
|
+
"startup_order": 2,
|
|
45
|
+
"depends_on": [
|
|
46
|
+
"pmo"
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"id": "builder-2",
|
|
51
|
+
"nickname": "builder-2",
|
|
52
|
+
"type": "auto",
|
|
53
|
+
"role": "implement approved slices",
|
|
54
|
+
"prompt_profile": "implementation-lead",
|
|
55
|
+
"accept_from": [
|
|
56
|
+
"pmo",
|
|
57
|
+
"reviewer"
|
|
58
|
+
],
|
|
59
|
+
"report_to": [
|
|
60
|
+
"pmo",
|
|
61
|
+
"reviewer"
|
|
62
|
+
],
|
|
63
|
+
"startup_order": 2,
|
|
64
|
+
"depends_on": [
|
|
65
|
+
"pmo"
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"id": "builder-3",
|
|
70
|
+
"nickname": "builder-3",
|
|
71
|
+
"type": "auto",
|
|
72
|
+
"role": "implement approved slices",
|
|
73
|
+
"prompt_profile": "implementation-lead",
|
|
74
|
+
"accept_from": [
|
|
75
|
+
"pmo",
|
|
76
|
+
"reviewer"
|
|
77
|
+
],
|
|
78
|
+
"report_to": [
|
|
79
|
+
"pmo",
|
|
80
|
+
"reviewer"
|
|
81
|
+
],
|
|
82
|
+
"startup_order": 2,
|
|
83
|
+
"depends_on": [
|
|
84
|
+
"pmo"
|
|
85
|
+
]
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"id": "builder-4",
|
|
89
|
+
"nickname": "builder-4",
|
|
90
|
+
"type": "auto",
|
|
91
|
+
"role": "implement approved slices",
|
|
92
|
+
"prompt_profile": "implementation-lead",
|
|
93
|
+
"accept_from": [
|
|
94
|
+
"pmo",
|
|
95
|
+
"reviewer"
|
|
96
|
+
],
|
|
97
|
+
"report_to": [
|
|
98
|
+
"pmo",
|
|
99
|
+
"reviewer"
|
|
100
|
+
],
|
|
101
|
+
"startup_order": 2,
|
|
102
|
+
"depends_on": [
|
|
103
|
+
"pmo"
|
|
104
|
+
]
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"id": "reviewer",
|
|
108
|
+
"nickname": "reviewer",
|
|
109
|
+
"type": "auto",
|
|
110
|
+
"role": "review correctness and risk",
|
|
111
|
+
"prompt_profile": "review-critic",
|
|
112
|
+
"accept_from": [
|
|
113
|
+
"pmo",
|
|
114
|
+
"builder-1",
|
|
115
|
+
"builder-2",
|
|
116
|
+
"builder-3",
|
|
117
|
+
"builder-4"
|
|
118
|
+
],
|
|
119
|
+
"report_to": [
|
|
120
|
+
"pmo",
|
|
121
|
+
"builder-1",
|
|
122
|
+
"builder-2",
|
|
123
|
+
"builder-3",
|
|
124
|
+
"builder-4"
|
|
125
|
+
],
|
|
126
|
+
"startup_order": 3,
|
|
127
|
+
"depends_on": [
|
|
128
|
+
"pmo"
|
|
129
|
+
]
|
|
130
|
+
}
|
|
131
|
+
],
|
|
132
|
+
"edges": [
|
|
133
|
+
{
|
|
134
|
+
"from": "pmo",
|
|
135
|
+
"to": "builder-1",
|
|
136
|
+
"kind": "task"
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"from": "pmo",
|
|
140
|
+
"to": "builder-2",
|
|
141
|
+
"kind": "task"
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"from": "pmo",
|
|
145
|
+
"to": "builder-3",
|
|
146
|
+
"kind": "task"
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"from": "pmo",
|
|
150
|
+
"to": "builder-4",
|
|
151
|
+
"kind": "task"
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"from": "builder-1",
|
|
155
|
+
"to": "reviewer",
|
|
156
|
+
"kind": "review"
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"from": "builder-2",
|
|
160
|
+
"to": "reviewer",
|
|
161
|
+
"kind": "review"
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"from": "builder-3",
|
|
165
|
+
"to": "reviewer",
|
|
166
|
+
"kind": "review"
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"from": "builder-4",
|
|
170
|
+
"to": "reviewer",
|
|
171
|
+
"kind": "review"
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"from": "reviewer",
|
|
175
|
+
"to": "builder-1",
|
|
176
|
+
"kind": "task"
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"from": "reviewer",
|
|
180
|
+
"to": "builder-2",
|
|
181
|
+
"kind": "task"
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"from": "reviewer",
|
|
185
|
+
"to": "builder-3",
|
|
186
|
+
"kind": "task"
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"from": "reviewer",
|
|
190
|
+
"to": "builder-4",
|
|
191
|
+
"kind": "task"
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"from": "reviewer",
|
|
195
|
+
"to": "pmo",
|
|
196
|
+
"kind": "report"
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
"from": "builder-1",
|
|
200
|
+
"to": "pmo",
|
|
201
|
+
"kind": "report"
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"from": "builder-2",
|
|
205
|
+
"to": "pmo",
|
|
206
|
+
"kind": "report"
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
"from": "builder-3",
|
|
210
|
+
"to": "pmo",
|
|
211
|
+
"kind": "report"
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
"from": "builder-4",
|
|
215
|
+
"to": "pmo",
|
|
216
|
+
"kind": "report"
|
|
217
|
+
}
|
|
218
|
+
]
|
|
219
|
+
}
|